Merge "Adding support for async view loading in RemoteViewsAdapter"
diff --git a/Android.mk b/Android.mk
index 121a38e..561d220 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 \
@@ -288,7 +289,6 @@
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
- core/java/android/view/IAssetAtlas.aidl \
core/java/android/view/IDockedStackListener.aidl \
core/java/android/view/IGraphicsStats.aidl \
core/java/android/view/IInputFilter.aidl \
@@ -332,6 +332,7 @@
core/java/com/android/internal/os/IDropBoxManagerService.aidl \
core/java/com/android/internal/os/IParcelFileDescriptorFactory.aidl \
core/java/com/android/internal/os/IResultReceiver.aidl \
+ core/java/com/android/internal/os/IShellCallback.aidl \
core/java/com/android/internal/statusbar/IStatusBar.aidl \
core/java/com/android/internal/statusbar/IStatusBarService.aidl \
core/java/com/android/internal/textservice/ISpellCheckerService.aidl \
@@ -502,7 +503,10 @@
LOCAL_NO_STANDARD_LIBRARIES := true
LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp core-junit bouncycastle ext
-LOCAL_STATIC_JAVA_LIBRARIES := framework-protos
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ framework-protos \
+ android.hardware.thermal@1.0-java-constants \
LOCAL_MODULE := framework
@@ -574,6 +578,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 +602,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 \
@@ -859,6 +865,7 @@
-since $(SRC_API_DIR)/22.txt 22 \
-since $(SRC_API_DIR)/23.txt 23 \
-since $(SRC_API_DIR)/24.txt 24 \
+ -since $(SRC_API_DIR)/25.txt 25 \
-werror -hide 111 -hide 113 \
-overview $(LOCAL_PATH)/core/java/overview.html
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..c28db57
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +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/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/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index eb07c05..5fe2f02 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -12,5 +12,8 @@
LOCAL_PACKAGE_NAME := CorePerfTests
+# Use google-fonts/dancing-script for the performance metrics
+LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
+
include $(BUILD_PACKAGE)
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
new file mode 100644
index 0000000..519d1f4
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.graphics.perftests;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Bitmap.Config;
+import android.graphics.Paint;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class CanvasPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testBasicViewGroupDraw() {
+ // This test is a clone of BM_DisplayListCanvas_basicViewGroupDraw
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ RenderNode node = RenderNode.create("benchmark", null);
+ RenderNode child = RenderNode.create("child", null);
+ child.setLeftTopRightBottom(50, 50, 100, 100);
+
+ DisplayListCanvas canvas = node.start(100, 100);
+ node.end(canvas);
+ canvas = child.start(50, 50);
+ canvas.drawColor(Color.WHITE);
+ child.end(canvas);
+
+ while (state.keepRunning()) {
+ canvas = node.start(200, 200);
+ canvas.setHighContrastText(false);
+ int save = canvas.save();
+ canvas.clipRect(1, 1, 199, 199);
+ canvas.insertReorderBarrier();
+ for (int i = 0; i < 5; i++) {
+ canvas.drawRenderNode(child);
+ }
+ canvas.insertInorderBarrier();
+ canvas.restoreToCount(save);
+ node.end(canvas);
+ }
+ }
+
+ @Test
+ public void testRecordSimpleBitmapView() {
+ // This test is a clone of BM_DisplayListCanvas_record_simpleBitmapView
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ RenderNode node = RenderNode.create("benchmark", null);
+
+ DisplayListCanvas canvas = node.start(100, 100);
+ node.end(canvas);
+ Bitmap bitmap = Bitmap.createBitmap(80, 80, Config.ARGB_8888);
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+
+ while (state.keepRunning()) {
+ canvas = node.start(100, 100);
+ {
+ canvas.save();
+ canvas.drawRect(0, 0, 100, 100, paint);
+ canvas.restore();
+ }
+ {
+ canvas.save();
+ canvas.translate(10, 10);
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ canvas.restore();
+ }
+ node.end(canvas);
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
new file mode 100644
index 0000000..11ee599
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.graphics.perftests;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TypefaceCreatePerfTest {
+ // A font file name in asset directory.
+ private static final String TEST_FONT_NAME = "DancingScript-Regular.ttf";
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testCreate_fromFamily() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ Typeface face = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
+ }
+ }
+
+ @Test
+ public void testCreate_fromFamilyName() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ Typeface face = Typeface.create("monospace", Typeface.NORMAL);
+ }
+ }
+
+ @Test
+ public void testCreate_fromAsset() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getContext();
+ final AssetManager am = context.getAssets();
+
+ while (state.keepRunning()) {
+ Typeface face = Typeface.createFromAsset(am, TEST_FONT_NAME);
+ }
+ }
+
+ @Test
+ public void testCreate_fromFile() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getContext();
+ final AssetManager am = context.getAssets();
+
+ File outFile = null;
+ try {
+ outFile = File.createTempFile("example", "ttf", context.getCacheDir());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ try (InputStream in = am.open(TEST_FONT_NAME);
+ OutputStream out = new FileOutputStream(outFile)) {
+ byte[] buf = new byte[1024];
+ int n = 0;
+ while ((n = in.read(buf)) != -1) {
+ out.write(buf, 0, n);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ while (state.keepRunning()) {
+ Typeface face = Typeface.createFromFile(outFile);
+ }
+
+ outFile.delete();
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java
new file mode 100644
index 0000000..a67aeca
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/ParcelArrayPerfTest.java
@@ -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.os;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class ParcelArrayPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameters(name = "size={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {1}, {10}, {100}, {1000} });
+ }
+
+ private final int mSize;
+
+ private Parcel mWriteParcel;
+
+ private byte[] mByteArray;
+ private int[] mIntArray;
+ private long[] mLongArray;
+
+ private Parcel mByteParcel;
+ private Parcel mIntParcel;
+ private Parcel mLongParcel;
+
+ public ParcelArrayPerfTest(int size) {
+ mSize = size;
+ }
+
+ @Before
+ public void setUp() {
+ mWriteParcel = Parcel.obtain();
+
+ mByteArray = new byte[mSize];
+ mIntArray = new int[mSize];
+ mLongArray = new long[mSize];
+
+ mByteParcel = Parcel.obtain();
+ mByteParcel.writeByteArray(mByteArray);
+ mIntParcel = Parcel.obtain();
+ mIntParcel.writeIntArray(mIntArray);
+ mLongParcel = Parcel.obtain();
+ mLongParcel.writeLongArray(mLongArray);
+ }
+
+ @After
+ public void tearDown() {
+ mWriteParcel.recycle();
+ mWriteParcel = null;
+ }
+
+ @Test
+ public void timeWriteByteArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mWriteParcel.setDataPosition(0);
+ mWriteParcel.writeByteArray(mByteArray);
+ }
+ }
+
+ @Test
+ public void timeCreateByteArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mByteParcel.setDataPosition(0);
+ mByteParcel.createByteArray();
+ }
+ }
+
+ @Test
+ public void timeReadByteArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mByteParcel.setDataPosition(0);
+ mByteParcel.readByteArray(mByteArray);
+ }
+ }
+
+ @Test
+ public void timeWriteIntArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mWriteParcel.setDataPosition(0);
+ mWriteParcel.writeIntArray(mIntArray);
+ }
+ }
+
+ @Test
+ public void timeCreateIntArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mIntParcel.setDataPosition(0);
+ mIntParcel.createIntArray();
+ }
+ }
+
+ @Test
+ public void timeReadIntArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mIntParcel.setDataPosition(0);
+ mIntParcel.readIntArray(mIntArray);
+ }
+ }
+
+ @Test
+ public void timeWriteLongArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mWriteParcel.setDataPosition(0);
+ mWriteParcel.writeLongArray(mLongArray);
+ }
+ }
+
+ @Test
+ public void timeCreateLongArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mLongParcel.setDataPosition(0);
+ mLongParcel.createLongArray();
+ }
+ }
+
+ @Test
+ public void timeReadLongArray() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mLongParcel.setDataPosition(0);
+ mLongParcel.readLongArray(mLongArray);
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
new file mode 100644
index 0000000..8cd45f7
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ParcelPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private Parcel mParcel;
+
+ @Before
+ public void setUp() {
+ mParcel = Parcel.obtain();
+ mParcel.setDataPosition(0);
+ mParcel.setDataCapacity(8);
+ }
+
+ @After
+ public void tearDown() {
+ mParcel.recycle();
+ mParcel = null;
+ }
+
+ @Test
+ public void timeSetDataPosition() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ }
+ }
+
+ @Test
+ public void timeWriteByte() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final byte val = 0xF;
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ mParcel.writeByte(val);
+ }
+ }
+
+ @Test
+ public void timeReadByte() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ mParcel.readByte();
+ }
+ }
+
+ @Test
+ public void timeWriteInt() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final int val = 0xF;
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ mParcel.writeInt(val);
+ }
+ }
+
+ @Test
+ public void timeReadInt() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ mParcel.readInt();
+ }
+ }
+
+ @Test
+ public void timeWriteLong() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final long val = 0xF;
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ mParcel.writeLong(val);
+ }
+ }
+
+ @Test
+ public void timeReadLong() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.setDataPosition(0);
+ mParcel.readLong();
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java b/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java
index 6a49c03..1e9c49c 100644
--- a/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java
+++ b/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java
@@ -24,6 +24,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
@LargeTest
public class SystemPerfTest {
@@ -37,4 +39,25 @@
System.nanoTime();
}
}
+
+ @Test
+ public void testBenchmarkOverhead() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {}
+ }
+
+ void spinBlock(long durationNs) {
+ long start = System.nanoTime();
+ while (System.nanoTime() - start < durationNs) {}
+ }
+
+ @Test
+ public void testBenchmarkPauseResumeOverhead() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ spinBlock(TimeUnit.MICROSECONDS.toNanos(5));
+ state.resumeTiming();
+ }
+ }
}
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java
index cdbca63..88cb8e6 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java
@@ -57,8 +57,6 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class UserLifecycleTest {
- private final int MIN_REPEAT_TIMES = 4;
-
private final int TIMEOUT_REMOVE_USER_MS = 4 * 1000; // 4 sec
private final int CHECK_USER_REMOVED_INTERVAL_MS = 200; // 0.2 sec
@@ -90,7 +88,6 @@
mAm = context.getSystemService(ActivityManager.class);
mIam = ActivityManagerNative.getDefault();
mState = mPerfStatusReporter.getBenchmarkState();
- mState.setMinRepeatTimes(MIN_REPEAT_TIMES);
mUsersToRemove = new ArrayList<>();
}
@@ -300,4 +297,4 @@
mUsersToRemove.add(userId);
}
}
-}
\ No newline at end of file
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
index b27d71b..519d524 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
@@ -23,6 +23,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
/**
* Provides a benchmark framework.
@@ -41,39 +42,48 @@
* System.out.println(state.summaryLine());
* }
*/
-public class BenchmarkState {
+public final class BenchmarkState {
+
private static final String TAG = "BenchmarkState";
- private static final int NOT_STARTED = 1; // The benchmark has not started yet.
+ private static final int NOT_STARTED = 0; // The benchmark has not started yet.
+ private static final int WARMUP = 1; // The benchmark is warming up.
private static final int RUNNING = 2; // The benchmark is running.
- private static final int RUNNING_PAUSED = 3; // The benchmark is temporary paused.
- private static final int FINISHED = 4; // The benchmark has stopped.
+ private static final int FINISHED = 3; // The benchmark has stopped.
private int mState = NOT_STARTED; // Current benchmark state.
- private long mNanoPreviousTime = 0; // Previously captured System.nanoTime().
- private long mNanoFinishTime = 0; // Finish if System.nanoTime() returns after than this value.
- private long mNanoPausedTime = 0; // The System.nanoTime() when the pauseTiming() is called.
- private long mNanoPausedDuration = 0; // The duration of paused state in nano sec.
- private long mNanoTimeLimit = 1 * 1000 * 1000 * 1000; // 1 sec. Default time limit.
+ private static final long WARMUP_DURATION_NS = ms2ns(250); // warm-up for at least 250ms
+ private static final int WARMUP_MIN_ITERATIONS = 16; // minimum iterations to warm-up for
+
+ // TODO: Tune these values.
+ private static final long TARGET_TEST_DURATION_NS = ms2ns(500); // target testing for 500 ms
+ private static final int MAX_TEST_ITERATIONS = 1000000;
+ private static final int MIN_TEST_ITERATIONS = 100;
+ private static final int REPEAT_COUNT = 5;
+
+ private long mStartTimeNs = 0; // Previously captured System.nanoTime().
+ private boolean mPaused;
+ private long mPausedTimeNs = 0; // The System.nanoTime() when the pauseTiming() is called.
+ private long mPausedDurationNs = 0; // The duration of paused state in nano sec.
+
+ private int mIteration = 0;
+ private int mMaxIterations = 0;
+
+ private int mRepeatCount = 0;
// Statistics. These values will be filled when the benchmark has finished.
// The computation needs double precision, but long int is fine for final reporting.
private long mMedian = 0;
private double mMean = 0.0;
private double mStandardDeviation = 0.0;
-
- // Number of iterations needed for calculating the stats.
- private int mMinRepeatTimes = 16;
+ private long mMin = 0;
// Individual duration in nano seconds.
private ArrayList<Long> mResults = new ArrayList<>();
- /**
- * Sets the number of iterations needed for calculating the stats. Default is 16.
- */
- public void setMinRepeatTimes(int minRepeatTimes) {
- mMinRepeatTimes = minRepeatTimes;
+ private static final long ms2ns(long ms) {
+ return TimeUnit.MILLISECONDS.toNanos(ms);
}
/**
@@ -89,8 +99,13 @@
mMedian = size % 2 == 0 ? (mResults.get(size / 2) + mResults.get(size / 2 + 1)) / 2 :
mResults.get(size / 2);
+ mMin = mResults.get(0);
for (int i = 0; i < size; ++i) {
- mMean += mResults.get(i);
+ long result = mResults.get(i);
+ mMean += result;
+ if (result < mMin) {
+ mMin = result;
+ }
}
mMean /= (double) size;
@@ -104,24 +119,56 @@
// Stops the benchmark timer.
// This method can be called only when the timer is running.
public void pauseTiming() {
- if (mState == RUNNING_PAUSED) {
+ if (mPaused) {
throw new IllegalStateException(
"Unable to pause the benchmark. The benchmark has already paused.");
}
- mNanoPausedTime = System.nanoTime();
- mState = RUNNING_PAUSED;
+ mPausedTimeNs = System.nanoTime();
+ mPaused = true;
}
// Starts the benchmark timer.
// This method can be called only when the timer is stopped.
public void resumeTiming() {
- if (mState == RUNNING) {
+ if (!mPaused) {
throw new IllegalStateException(
"Unable to resume the benchmark. The benchmark is already running.");
}
- mNanoPausedDuration += System.nanoTime() - mNanoPausedTime;
- mNanoPausedTime = 0;
+ mPausedDurationNs += System.nanoTime() - mPausedTimeNs;
+ mPausedTimeNs = 0;
+ mPaused = false;
+ }
+
+ private void beginWarmup() {
+ mStartTimeNs = System.nanoTime();
+ mIteration = 0;
+ mState = WARMUP;
+ }
+
+ private void beginBenchmark(long warmupDuration, int iterations) {
+ mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
+ mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
+ Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
+ mPausedDurationNs = 0;
+ mIteration = 0;
+ mRepeatCount = 0;
mState = RUNNING;
+ mStartTimeNs = System.nanoTime();
+ }
+
+ private boolean startNextTestRun() {
+ final long currentTime = System.nanoTime();
+ mResults.add((currentTime - mStartTimeNs - mPausedDurationNs) / mMaxIterations);
+ mRepeatCount++;
+ if (mRepeatCount >= REPEAT_COUNT) {
+ calculateSatistics();
+ mState = FINISHED;
+ return false;
+ }
+ mPausedDurationNs = 0;
+ mIteration = 0;
+ mStartTimeNs = System.nanoTime();
+ return true;
}
/**
@@ -132,28 +179,28 @@
public boolean keepRunning() {
switch (mState) {
case NOT_STARTED:
- mNanoPreviousTime = System.nanoTime();
- mNanoFinishTime = mNanoPreviousTime + mNanoTimeLimit;
- mState = RUNNING;
+ beginWarmup();
+ return true;
+ case WARMUP:
+ mIteration++;
+ // Only check nanoTime on every iteration in WARMUP since we
+ // don't yet have a target iteration count.
+ final long duration = System.nanoTime() - mStartTimeNs;
+ if (mIteration >= WARMUP_MIN_ITERATIONS && duration >= WARMUP_DURATION_NS) {
+ beginBenchmark(duration, mIteration);
+ }
return true;
case RUNNING:
- final long currentTime = System.nanoTime();
- mResults.add(currentTime - mNanoPreviousTime - mNanoPausedDuration);
- mNanoPausedDuration = 0;
-
- // To calculate statistics, needs two or more samples.
- if (mResults.size() > mMinRepeatTimes && currentTime > mNanoFinishTime) {
- calculateSatistics();
- mState = FINISHED;
- return false;
+ mIteration++;
+ if (mIteration >= mMaxIterations) {
+ return startNextTestRun();
}
-
- mNanoPreviousTime = currentTime;
+ if (mPaused) {
+ throw new IllegalStateException(
+ "Benchmark step finished with paused state. " +
+ "Resume the benchmark before finishing each step.");
+ }
return true;
- case RUNNING_PAUSED:
- throw new IllegalStateException(
- "Benchmark step finished with paused state. " +
- "Resume the benchmark before finishing each step.");
case FINISHED:
throw new IllegalStateException("The benchmark has finished.");
default:
@@ -161,21 +208,28 @@
}
}
- public long mean() {
+ private long mean() {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
return (long) mMean;
}
- public long median() {
+ private long median() {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
return mMedian;
}
- public long standardDeviation() {
+ private long min() {
+ if (mState != FINISHED) {
+ throw new IllegalStateException("The benchmark hasn't finished");
+ }
+ return mMin;
+ }
+
+ private long standardDeviation() {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
@@ -187,10 +241,11 @@
sb.append("Summary: ");
sb.append("median=").append(median()).append("ns, ");
sb.append("mean=").append(mean()).append("ns, ");
+ sb.append("min=").append(min()).append("ns, ");
sb.append("sigma=").append(standardDeviation()).append(", ");
sb.append("iteration=").append(mResults.size()).append(", ");
// print out the first few iterations' number for double checking.
- int sampleNumber = Math.min(mResults.size(), mMinRepeatTimes);
+ int sampleNumber = Math.min(mResults.size(), 16);
for (int i = 0; i < sampleNumber; i++) {
sb.append("No ").append(i).append(" result is ").append(mResults.get(i)).append(", ");
}
@@ -202,6 +257,7 @@
Bundle status = new Bundle();
status.putLong(key + "_median", median());
status.putLong(key + "_mean", mean());
+ status.putLong(key + "_min", min());
status.putLong(key + "_standardDeviation", standardDeviation());
instrumentation.sendStatus(Activity.RESULT_OK, status);
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfStatusReporter.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfStatusReporter.java
index 3933b57..64b0bf5 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/PerfStatusReporter.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfStatusReporter.java
@@ -18,8 +18,9 @@
import android.support.test.InstrumentationRegistry;
-import org.junit.rules.TestWatcher;
+import org.junit.rules.TestRule;
import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -47,7 +48,7 @@
* name when using parameterization.
*/
-public class PerfStatusReporter extends TestWatcher {
+public class PerfStatusReporter implements TestRule {
private final BenchmarkState mState = new BenchmarkState();
public BenchmarkState getBenchmarkState() {
@@ -55,33 +56,39 @@
}
@Override
- protected void succeeded(Description description) {
- String invokeMethodName = description.getMethodName();
- // validate and simplify the function name.
- // First, remove the "test" prefix which normally comes from CTS test.
- // Then make sure the [subTestName] is valid, not just numbers like [0].
- if (invokeMethodName.startsWith("test")) {
- assertTrue("The test name " + invokeMethodName + " is too short",
- invokeMethodName.length() > 5);
- invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase()
- + invokeMethodName.substring(5);
- }
-
- int index = invokeMethodName.lastIndexOf('[');
- if (index > 0) {
- boolean allDigits = true;
- for (int i = index + 1; i < invokeMethodName.length() - 1; i++) {
- if (!Character.isDigit(invokeMethodName.charAt(i))) {
- allDigits = false;
- break;
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ String invokeMethodName = description.getMethodName();
+ // validate and simplify the function name.
+ // First, remove the "test" prefix which normally comes from CTS test.
+ // Then make sure the [subTestName] is valid, not just numbers like [0].
+ if (invokeMethodName.startsWith("test")) {
+ assertTrue("The test name " + invokeMethodName + " is too short",
+ invokeMethodName.length() > 5);
+ invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase()
+ + invokeMethodName.substring(5);
}
+
+ int index = invokeMethodName.lastIndexOf('[');
+ if (index > 0) {
+ boolean allDigits = true;
+ for (int i = index + 1; i < invokeMethodName.length() - 1; i++) {
+ if (!Character.isDigit(invokeMethodName.charAt(i))) {
+ allDigits = false;
+ break;
+ }
+ }
+ assertFalse("The name in [] can't contain only digits for " + invokeMethodName,
+ allDigits);
+ }
+
+ base.evaluate();
+
+ mState.sendFullStatusReport(InstrumentationRegistry.getInstrumentation(),
+ invokeMethodName);
}
- assertFalse("The name in [] can't contain only digits for " + invokeMethodName,
- allDigits);
- }
-
- mState.sendFullStatusReport(InstrumentationRegistry.getInstrumentation(),
- invokeMethodName);
+ };
}
-
}
diff --git a/api/current.txt b/api/current.txt
index 2d3fde8..4c98263 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -855,6 +855,7 @@
field public static final int mediaRouteTypes = 16843694; // 0x10103ae
field public static final int menuCategory = 16843230; // 0x10101de
field public static final int mimeType = 16842790; // 0x1010026
+ field public static final int min = 16843367; // 0x1010267
field public static final int minDate = 16843583; // 0x101033f
field public static final int minEms = 16843098; // 0x101015a
field public static final int minHeight = 16843072; // 0x1010140
@@ -3265,6 +3266,7 @@
public class ValueAnimator extends android.animation.Animator {
ctor public ValueAnimator();
method public void addUpdateListener(android.animation.ValueAnimator.AnimatorUpdateListener);
+ method public static boolean areAnimatorsEnabled();
method public float getAnimatedFraction();
method public java.lang.Object getAnimatedValue();
method public java.lang.Object getAnimatedValue(java.lang.String);
@@ -4042,6 +4044,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
field public static final java.lang.String OPSTR_READ_CELL_BROADCASTS = "android:read_cell_broadcasts";
@@ -4486,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);
@@ -4511,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);
}
@@ -4668,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);
@@ -13896,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);
@@ -14774,6 +14781,7 @@
method public java.lang.String getSerial();
method public boolean releaseInterface(android.hardware.usb.UsbInterface);
method public android.hardware.usb.UsbRequest requestWait();
+ method public android.hardware.usb.UsbRequest requestWait(int);
method public boolean setConfiguration(android.hardware.usb.UsbConfiguration);
method public boolean setInterface(android.hardware.usb.UsbInterface);
}
@@ -21461,6 +21469,7 @@
field public static final int AMR_NB = 3; // 0x3
field public static final int AMR_WB = 4; // 0x4
field public static final int DEFAULT = 0; // 0x0
+ field public static final int MPEG_2_TS = 8; // 0x8
field public static final int MPEG_4 = 2; // 0x2
field public static final deprecated int RAW_AMR = 3; // 0x3
field public static final int THREE_GPP = 1; // 0x1
@@ -30305,6 +30314,8 @@
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getAdvancedIntOption(java.lang.String);
+ method public java.lang.String getAdvancedStringOption(java.lang.String);
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public long getCreationTime();
@@ -30313,6 +30324,7 @@
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
method public int getState();
+ method public boolean hasAdvancedOption(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
field public static final int STATE_BLOCKED = 4; // 0x4
@@ -30456,9 +30468,11 @@
method protected void onDisconnected();
method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
+ field public static final java.lang.String EXTRA_CAN_SELECT_PRINTER = "android.printservice.extra.CAN_SELECT_PRINTER";
field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
field public static final java.lang.String EXTRA_PRINT_DOCUMENT_INFO = "android.printservice.extra.PRINT_DOCUMENT_INFO";
field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
+ field public static final java.lang.String EXTRA_SELECT_PRINTER = "android.printservice.extra.SELECT_PRINTER";
field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
}
@@ -31676,6 +31690,7 @@
method public static void notifyDirectoryChange(android.content.ContentResolver);
field public static final java.lang.String ACCOUNT_NAME = "accountName";
field public static final java.lang.String ACCOUNT_TYPE = "accountType";
+ field public static final java.lang.String CALLER_PACKAGE_PARAM_KEY = "callerPackage";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
field public static final android.net.Uri CONTENT_URI;
@@ -32570,7 +32585,7 @@
field public static final java.lang.String RADIO_CELL = "cell";
field public static final java.lang.String RADIO_NFC = "nfc";
field public static final java.lang.String RADIO_WIFI = "wifi";
- field public static final java.lang.String SHOW_PROCESSES = "show_processes";
+ field public static final deprecated java.lang.String SHOW_PROCESSES = "show_processes";
field public static final java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
field public static final java.lang.String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
field public static final java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
@@ -34846,6 +34861,7 @@
method public static void requestRebind(android.content.ComponentName);
method public final void requestUnbind();
method public final void setNotificationsShown(java.lang.String[]);
+ method public final void snoozeNotification(java.lang.String, long);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -36892,6 +36908,7 @@
field public static final java.lang.String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final java.lang.String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+ field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
@@ -36915,6 +36932,7 @@
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -36958,6 +36976,7 @@
field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
+ field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
@@ -36968,6 +36987,7 @@
field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+ field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
@@ -44009,6 +44029,7 @@
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_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
@@ -45834,6 +45855,7 @@
method public deprecated void freeMemory();
method public android.net.http.SslCertificate getCertificate();
method public int getContentHeight();
+ method public static android.content.pm.PackageInfo getCurrentWebViewPackage();
method public android.graphics.Bitmap getFavicon();
method public android.webkit.WebView.HitTestResult getHitTestResult();
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
@@ -45843,6 +45865,8 @@
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
method public java.lang.String getUrl();
+ method public android.webkit.WebChromeClient getWebChromeClient();
+ method public android.webkit.WebViewClient getWebViewClient();
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
@@ -47465,6 +47489,7 @@
method public android.graphics.PorterDuff.Mode getIndeterminateTintMode();
method public android.view.animation.Interpolator getInterpolator();
method public synchronized int getMax();
+ method public synchronized int getMin();
method public synchronized int getProgress();
method public android.content.res.ColorStateList getProgressBackgroundTintList();
method public android.graphics.PorterDuff.Mode getProgressBackgroundTintMode();
@@ -47487,6 +47512,7 @@
method public void setInterpolator(android.content.Context, int);
method public void setInterpolator(android.view.animation.Interpolator);
method public synchronized void setMax(int);
+ method public synchronized void setMin(int);
method public synchronized void setProgress(int);
method public void setProgress(int, boolean);
method public void setProgressBackgroundTintList(android.content.res.ColorStateList);
@@ -52070,6 +52096,8 @@
enum_constant public static final java.lang.annotation.ElementType PACKAGE;
enum_constant public static final java.lang.annotation.ElementType PARAMETER;
enum_constant public static final java.lang.annotation.ElementType TYPE;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_PARAMETER;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_USE;
}
public class IncompleteAnnotationException extends java.lang.RuntimeException {
@@ -52151,7 +52179,7 @@
method public abstract <T extends java.lang.annotation.Annotation> T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(java.lang.Class<T>);
- method public default <T extends java.lang.annotation.Annotation> java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
+ method public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public default boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
@@ -53276,8 +53304,6 @@
method public java.net.URLConnection openConnection(java.net.Proxy) throws java.io.IOException;
method public final java.io.InputStream openStream() throws java.io.IOException;
method public boolean sameFile(java.net.URL);
- method protected void set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String);
- method protected void set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory);
method public java.lang.String toExternalForm();
method public java.net.URI toURI() throws java.net.URISyntaxException;
@@ -59420,8 +59446,10 @@
method public java.util.Set<java.lang.String> getUnicodeLocaleKeys();
method public java.lang.String getUnicodeLocaleType(java.lang.String);
method public java.lang.String getVariant();
+ method public boolean hasExtensions();
method public static synchronized void setDefault(java.util.Locale);
method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale);
+ method public java.util.Locale stripExtensions();
method public java.lang.String toLanguageTag();
method public final java.lang.String toString();
field public static final java.util.Locale CANADA;
diff --git a/api/system-current.txt b/api/system-current.txt
index d0116ee..ce115d8 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";
@@ -962,6 +961,7 @@
field public static final int mediaRouteTypes = 16843694; // 0x10103ae
field public static final int menuCategory = 16843230; // 0x10101de
field public static final int mimeType = 16842790; // 0x1010026
+ field public static final int min = 16843367; // 0x1010267
field public static final int minDate = 16843583; // 0x101033f
field public static final int minEms = 16843098; // 0x101015a
field public static final int minHeight = 16843072; // 0x1010140
@@ -3380,6 +3380,7 @@
public class ValueAnimator extends android.animation.Animator {
ctor public ValueAnimator();
method public void addUpdateListener(android.animation.ValueAnimator.AnimatorUpdateListener);
+ method public static boolean areAnimatorsEnabled();
method public float getAnimatedFraction();
method public java.lang.Object getAnimatedValue();
method public java.lang.Object getAnimatedValue(java.lang.String);
@@ -4172,6 +4173,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
field public static final java.lang.String OPSTR_READ_CELL_BROADCASTS = "android:read_cell_broadcasts";
@@ -4631,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);
@@ -4656,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);
}
@@ -4813,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);
@@ -14352,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);
@@ -15984,6 +15990,7 @@
method public java.lang.String getSerial();
method public boolean releaseInterface(android.hardware.usb.UsbInterface);
method public android.hardware.usb.UsbRequest requestWait();
+ method public android.hardware.usb.UsbRequest requestWait(int);
method public boolean resetDevice();
method public boolean setConfiguration(android.hardware.usb.UsbConfiguration);
method public boolean setInterface(android.hardware.usb.UsbInterface);
@@ -22993,6 +23000,7 @@
field public static final int AMR_NB = 3; // 0x3
field public static final int AMR_WB = 4; // 0x4
field public static final int DEFAULT = 0; // 0x0
+ field public static final int MPEG_2_TS = 8; // 0x8
field public static final int MPEG_4 = 2; // 0x2
field public static final deprecated int RAW_AMR = 3; // 0x3
field public static final int THREE_GPP = 1; // 0x1
@@ -32905,6 +32913,8 @@
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getAdvancedIntOption(java.lang.String);
+ method public java.lang.String getAdvancedStringOption(java.lang.String);
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public long getCreationTime();
@@ -32913,6 +32923,7 @@
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
method public int getState();
+ method public boolean hasAdvancedOption(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
field public static final int STATE_BLOCKED = 4; // 0x4
@@ -33056,9 +33067,11 @@
method protected void onDisconnected();
method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
+ field public static final java.lang.String EXTRA_CAN_SELECT_PRINTER = "android.printservice.extra.CAN_SELECT_PRINTER";
field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
field public static final java.lang.String EXTRA_PRINT_DOCUMENT_INFO = "android.printservice.extra.PRINT_DOCUMENT_INFO";
field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
+ field public static final java.lang.String EXTRA_SELECT_PRINTER = "android.printservice.extra.SELECT_PRINTER";
field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
}
@@ -34300,6 +34313,7 @@
method public static void notifyDirectoryChange(android.content.ContentResolver);
field public static final java.lang.String ACCOUNT_NAME = "accountName";
field public static final java.lang.String ACCOUNT_TYPE = "accountType";
+ field public static final java.lang.String CALLER_PACKAGE_PARAM_KEY = "callerPackage";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
field public static final android.net.Uri CONTENT_URI;
@@ -35327,7 +35341,7 @@
field public static final java.lang.String RADIO_CELL = "cell";
field public static final java.lang.String RADIO_NFC = "nfc";
field public static final java.lang.String RADIO_WIFI = "wifi";
- field public static final java.lang.String SHOW_PROCESSES = "show_processes";
+ field public static final deprecated java.lang.String SHOW_PROCESSES = "show_processes";
field public static final java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
field public static final java.lang.String THEATER_MODE_ON = "theater_mode_on";
field public static final java.lang.String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
@@ -37546,8 +37560,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 {
@@ -37626,6 +37638,7 @@
method public final void requestUnbind();
method public final void setNotificationsShown(java.lang.String[]);
method public final void setOnNotificationPostedTrim(int);
+ method public final void snoozeNotification(java.lang.String, long);
method public void unregisterAsSystemService() throws android.os.RemoteException;
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
@@ -37687,6 +37700,7 @@
field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
+ field public static final int REASON_SNOOZED = 18; // 0x12
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationRankerService";
@@ -39983,6 +39997,7 @@
field public static final java.lang.String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final java.lang.String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+ field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
@@ -40006,6 +40021,7 @@
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -40049,6 +40065,7 @@
field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
+ field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
@@ -40059,6 +40076,7 @@
field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+ field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
@@ -47192,6 +47210,7 @@
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_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
@@ -49110,6 +49129,7 @@
method public deprecated void freeMemory();
method public android.net.http.SslCertificate getCertificate();
method public int getContentHeight();
+ method public static android.content.pm.PackageInfo getCurrentWebViewPackage();
method public android.graphics.Bitmap getFavicon();
method public android.webkit.WebView.HitTestResult getHitTestResult();
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
@@ -49119,6 +49139,8 @@
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
method public java.lang.String getUrl();
+ method public android.webkit.WebChromeClient getWebChromeClient();
+ method public android.webkit.WebViewClient getWebViewClient();
method public android.webkit.WebViewProvider getWebViewProvider();
method public void goBack();
method public void goBackOrForward(int);
@@ -49399,6 +49421,8 @@
method public abstract java.lang.String getUrl();
method public abstract android.webkit.WebViewProvider.ViewDelegate getViewDelegate();
method public abstract int getVisibleTitleHeight();
+ method public abstract android.webkit.WebChromeClient getWebChromeClient();
+ method public abstract android.webkit.WebViewClient getWebViewClient();
method public abstract android.view.View getZoomControls();
method public abstract void goBack();
method public abstract void goBackOrForward(int);
@@ -51002,6 +51026,7 @@
method public android.graphics.PorterDuff.Mode getIndeterminateTintMode();
method public android.view.animation.Interpolator getInterpolator();
method public synchronized int getMax();
+ method public synchronized int getMin();
method public synchronized int getProgress();
method public android.content.res.ColorStateList getProgressBackgroundTintList();
method public android.graphics.PorterDuff.Mode getProgressBackgroundTintMode();
@@ -51024,6 +51049,7 @@
method public void setInterpolator(android.content.Context, int);
method public void setInterpolator(android.view.animation.Interpolator);
method public synchronized void setMax(int);
+ method public synchronized void setMin(int);
method public synchronized void setProgress(int);
method public void setProgress(int, boolean);
method public void setProgressBackgroundTintList(android.content.res.ColorStateList);
@@ -55607,6 +55633,8 @@
enum_constant public static final java.lang.annotation.ElementType PACKAGE;
enum_constant public static final java.lang.annotation.ElementType PARAMETER;
enum_constant public static final java.lang.annotation.ElementType TYPE;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_PARAMETER;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_USE;
}
public class IncompleteAnnotationException extends java.lang.RuntimeException {
@@ -55688,7 +55716,7 @@
method public abstract <T extends java.lang.annotation.Annotation> T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(java.lang.Class<T>);
- method public default <T extends java.lang.annotation.Annotation> java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
+ method public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public default boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
@@ -56813,8 +56841,6 @@
method public java.net.URLConnection openConnection(java.net.Proxy) throws java.io.IOException;
method public final java.io.InputStream openStream() throws java.io.IOException;
method public boolean sameFile(java.net.URL);
- method protected void set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String);
- method protected void set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory);
method public java.lang.String toExternalForm();
method public java.net.URI toURI() throws java.net.URISyntaxException;
@@ -62957,8 +62983,10 @@
method public java.util.Set<java.lang.String> getUnicodeLocaleKeys();
method public java.lang.String getUnicodeLocaleType(java.lang.String);
method public java.lang.String getVariant();
+ method public boolean hasExtensions();
method public static synchronized void setDefault(java.util.Locale);
method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale);
+ method public java.util.Locale stripExtensions();
method public java.lang.String toLanguageTag();
method public final java.lang.String toString();
field public static final java.util.Locale CANADA;
diff --git a/api/test-current.txt b/api/test-current.txt
index 9c744c5..92e5776 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -855,6 +855,7 @@
field public static final int mediaRouteTypes = 16843694; // 0x10103ae
field public static final int menuCategory = 16843230; // 0x10101de
field public static final int mimeType = 16842790; // 0x1010026
+ field public static final int min = 16843367; // 0x1010267
field public static final int minDate = 16843583; // 0x101033f
field public static final int minEms = 16843098; // 0x101015a
field public static final int minHeight = 16843072; // 0x1010140
@@ -3265,6 +3266,7 @@
public class ValueAnimator extends android.animation.Animator {
ctor public ValueAnimator();
method public void addUpdateListener(android.animation.ValueAnimator.AnimatorUpdateListener);
+ method public static boolean areAnimatorsEnabled();
method public float getAnimatedFraction();
method public java.lang.Object getAnimatedValue();
method public java.lang.Object getAnimatedValue(java.lang.String);
@@ -4045,6 +4047,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
field public static final java.lang.String OPSTR_READ_CELL_BROADCASTS = "android:read_cell_broadcasts";
@@ -4489,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);
@@ -4514,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);
}
@@ -4671,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);
@@ -13913,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);
@@ -14791,6 +14798,7 @@
method public java.lang.String getSerial();
method public boolean releaseInterface(android.hardware.usb.UsbInterface);
method public android.hardware.usb.UsbRequest requestWait();
+ method public android.hardware.usb.UsbRequest requestWait(int);
method public boolean setConfiguration(android.hardware.usb.UsbConfiguration);
method public boolean setInterface(android.hardware.usb.UsbInterface);
}
@@ -21534,6 +21542,7 @@
field public static final int AMR_NB = 3; // 0x3
field public static final int AMR_WB = 4; // 0x4
field public static final int DEFAULT = 0; // 0x0
+ field public static final int MPEG_2_TS = 8; // 0x8
field public static final int MPEG_4 = 2; // 0x2
field public static final deprecated int RAW_AMR = 3; // 0x3
field public static final int THREE_GPP = 1; // 0x1
@@ -30380,6 +30389,8 @@
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getAdvancedIntOption(java.lang.String);
+ method public java.lang.String getAdvancedStringOption(java.lang.String);
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public long getCreationTime();
@@ -30390,6 +30401,7 @@
method public float getProgress();
method public int getState();
method public java.lang.CharSequence getStatus(android.content.pm.PackageManager);
+ method public boolean hasAdvancedOption(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
field public static final int STATE_BLOCKED = 4; // 0x4
@@ -30534,9 +30546,11 @@
method protected void onDisconnected();
method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
+ field public static final java.lang.String EXTRA_CAN_SELECT_PRINTER = "android.printservice.extra.CAN_SELECT_PRINTER";
field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
field public static final java.lang.String EXTRA_PRINT_DOCUMENT_INFO = "android.printservice.extra.PRINT_DOCUMENT_INFO";
field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
+ field public static final java.lang.String EXTRA_SELECT_PRINTER = "android.printservice.extra.SELECT_PRINTER";
field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
}
@@ -31754,6 +31768,7 @@
method public static void notifyDirectoryChange(android.content.ContentResolver);
field public static final java.lang.String ACCOUNT_NAME = "accountName";
field public static final java.lang.String ACCOUNT_TYPE = "accountType";
+ field public static final java.lang.String CALLER_PACKAGE_PARAM_KEY = "callerPackage";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
field public static final android.net.Uri CONTENT_URI;
@@ -32648,7 +32663,7 @@
field public static final java.lang.String RADIO_CELL = "cell";
field public static final java.lang.String RADIO_NFC = "nfc";
field public static final java.lang.String RADIO_WIFI = "wifi";
- field public static final java.lang.String SHOW_PROCESSES = "show_processes";
+ field public static final deprecated java.lang.String SHOW_PROCESSES = "show_processes";
field public static final java.lang.String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
field public static final java.lang.String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
field public static final java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
@@ -34927,6 +34942,7 @@
method public static void requestRebind(android.content.ComponentName);
method public final void requestUnbind();
method public final void setNotificationsShown(java.lang.String[]);
+ method public final void snoozeNotification(java.lang.String, long);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -36973,6 +36989,7 @@
field public static final java.lang.String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final java.lang.String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+ field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
@@ -36996,6 +37013,7 @@
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -37039,6 +37057,7 @@
field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
+ field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
@@ -37049,6 +37068,7 @@
field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+ field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
@@ -41016,6 +41036,156 @@
}
+package android.util.proto {
+
+ public final class EncodedBuffer {
+ ctor public EncodedBuffer();
+ ctor public EncodedBuffer(int);
+ method public void dumpBuffers(java.lang.String);
+ method public static void dumpByteString(java.lang.String, java.lang.String, byte[]);
+ method public void editRawFixed32(int, int);
+ method public byte[] getBytes(int);
+ method public int getChunkCount();
+ method public java.lang.String getDebugString();
+ method public int getRawFixed32At(int);
+ method public static int getRawVarint32Size(int);
+ method public static int getRawVarint64Size(long);
+ method public static int getRawZigZag32Size(int);
+ method public static int getRawZigZag64Size(long);
+ method public int getReadPos();
+ method public int getReadableSize();
+ method public int getWriteBufIndex();
+ method public int getWriteIndex();
+ method public int getWritePos();
+ method public byte readRawByte();
+ method public int readRawFixed32();
+ method public long readRawUnsigned();
+ method public void rewindRead();
+ method public void rewindWriteTo(int);
+ method public void skipRead(int);
+ method public void startEditing();
+ method public void writeFromThisBuffer(int, int);
+ method public void writeRawBuffer(byte[]);
+ method public void writeRawBuffer(byte[], int, int);
+ method public void writeRawByte(byte);
+ method public void writeRawFixed32(int);
+ method public void writeRawFixed64(long);
+ method public void writeRawVarint32(int);
+ method public void writeRawVarint64(long);
+ method public void writeRawZigZag32(int);
+ method public void writeRawZigZag64(long);
+ }
+
+ public final class ProtoOutputStream {
+ ctor public ProtoOutputStream();
+ ctor public ProtoOutputStream(int);
+ method public static int checkFieldId(long, long);
+ method public static int convertObjectIdToOrdinal(int);
+ method public void dump(java.lang.String);
+ method public void endObject(long);
+ method public void endRepeatedObject(long);
+ method public byte[] getBytes();
+ method public static int getDepthFromToken(long);
+ method public static int getObjectIdFromToken(long);
+ method public static boolean getRepeatedFromToken(long);
+ method public static int getSizePosFromToken(long);
+ method public static int getTagSizeFromToken(long);
+ method public static long makeFieldId(int, long);
+ method public static long makeToken(int, boolean, int, int, int);
+ method public long startObject(long);
+ method public long startRepeatedObject(long);
+ method public static java.lang.String token2String(long);
+ method public void writeBool(long, boolean);
+ method public void writeBytes(long, byte[]);
+ method public void writeDouble(long, double);
+ method public void writeEnum(long, int);
+ method public void writeFixed32(long, int);
+ method public void writeFixed64(long, long);
+ method public void writeFloat(long, float);
+ method public void writeInt32(long, int);
+ method public void writeInt64(long, long);
+ method public void writePackedBool(long, boolean[]);
+ method public void writePackedDouble(long, double[]);
+ method public void writePackedEnum(long, int[]);
+ method public void writePackedFixed32(long, int[]);
+ method public void writePackedFixed64(long, long[]);
+ method public void writePackedFloat(long, float[]);
+ method public void writePackedInt32(long, int[]);
+ method public void writePackedInt64(long, long[]);
+ method public void writePackedSFixed32(long, int[]);
+ method public void writePackedSFixed64(long, long[]);
+ method public void writePackedSInt32(long, int[]);
+ method public void writePackedSInt64(long, long[]);
+ method public void writePackedUInt32(long, int[]);
+ method public void writePackedUInt64(long, long[]);
+ method public void writeRepeatedBool(long, boolean);
+ method public void writeRepeatedBytes(long, byte[]);
+ method public void writeRepeatedDouble(long, double);
+ method public void writeRepeatedEnum(long, int);
+ method public void writeRepeatedFixed32(long, int);
+ method public void writeRepeatedFixed64(long, long);
+ method public void writeRepeatedFloat(long, float);
+ method public void writeRepeatedInt32(long, int);
+ method public void writeRepeatedInt64(long, long);
+ method public void writeRepeatedSFixed32(long, int);
+ method public void writeRepeatedSFixed64(long, long);
+ method public void writeRepeatedSInt32(long, int);
+ method public void writeRepeatedSInt64(long, long);
+ method public void writeRepeatedString(long, java.lang.String);
+ method public void writeRepeatedUInt32(long, int);
+ method public void writeRepeatedUInt64(long, long);
+ method public void writeSFixed32(long, int);
+ method public void writeSFixed64(long, long);
+ method public void writeSInt32(long, int);
+ method public void writeSInt64(long, long);
+ method public void writeString(long, java.lang.String);
+ method public void writeTag(int, int);
+ method public void writeUInt32(long, int);
+ method public void writeUInt64(long, long);
+ field public static final long FIELD_COUNT_MASK = 16492674416640L; // 0xf0000000000L
+ field public static final long FIELD_COUNT_PACKED = 5497558138880L; // 0x50000000000L
+ field public static final long FIELD_COUNT_REPEATED = 2199023255552L; // 0x20000000000L
+ field public static final int FIELD_COUNT_SHIFT = 40; // 0x28
+ field public static final long FIELD_COUNT_SINGLE = 1099511627776L; // 0x10000000000L
+ field public static final long FIELD_COUNT_UNKNOWN = 0L; // 0x0L
+ field public static final int FIELD_ID_MASK = -8; // 0xfffffff8
+ field public static final int FIELD_ID_SHIFT = 3; // 0x3
+ field public static final long FIELD_TYPE_BOOL = 55834574848L; // 0xd00000000L
+ field public static final long FIELD_TYPE_BYTES = 64424509440L; // 0xf00000000L
+ field public static final long FIELD_TYPE_DOUBLE = 4294967296L; // 0x100000000L
+ field public static final long FIELD_TYPE_ENUM = 68719476736L; // 0x1000000000L
+ field public static final long FIELD_TYPE_FIXED32 = 38654705664L; // 0x900000000L
+ field public static final long FIELD_TYPE_FIXED64 = 42949672960L; // 0xa00000000L
+ field public static final long FIELD_TYPE_FLOAT = 8589934592L; // 0x200000000L
+ field public static final long FIELD_TYPE_INT32 = 12884901888L; // 0x300000000L
+ field public static final long FIELD_TYPE_INT64 = 17179869184L; // 0x400000000L
+ field public static final long FIELD_TYPE_MASK = 1095216660480L; // 0xff00000000L
+ field public static final long FIELD_TYPE_OBJECT = 73014444032L; // 0x1100000000L
+ field public static final long FIELD_TYPE_SFIXED32 = 47244640256L; // 0xb00000000L
+ field public static final long FIELD_TYPE_SFIXED64 = 51539607552L; // 0xc00000000L
+ field public static final int FIELD_TYPE_SHIFT = 32; // 0x20
+ field public static final long FIELD_TYPE_SINT32 = 30064771072L; // 0x700000000L
+ field public static final long FIELD_TYPE_SINT64 = 34359738368L; // 0x800000000L
+ field public static final long FIELD_TYPE_STRING = 60129542144L; // 0xe00000000L
+ field public static final long FIELD_TYPE_UINT32 = 21474836480L; // 0x500000000L
+ field public static final long FIELD_TYPE_UINT64 = 25769803776L; // 0x600000000L
+ field public static final long FIELD_TYPE_UNKNOWN = 0L; // 0x0L
+ field public static final java.lang.String TAG = "ProtoOutputStream";
+ field public static final int WIRE_TYPE_END_GROUP = 4; // 0x4
+ field public static final int WIRE_TYPE_FIXED32 = 5; // 0x5
+ field public static final int WIRE_TYPE_FIXED64 = 1; // 0x1
+ field public static final int WIRE_TYPE_LENGTH_DELIMITED = 2; // 0x2
+ field public static final int WIRE_TYPE_MASK = 7; // 0x7
+ field public static final int WIRE_TYPE_START_GROUP = 3; // 0x3
+ field public static final int WIRE_TYPE_VARINT = 0; // 0x0
+ }
+
+ public class ProtoParseException extends java.lang.RuntimeException {
+ ctor public ProtoParseException(java.lang.String);
+ }
+
+}
+
package android.view {
public abstract class AbsSavedState implements android.os.Parcelable {
@@ -44095,6 +44265,7 @@
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_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
@@ -45922,6 +46093,7 @@
method public deprecated void freeMemory();
method public android.net.http.SslCertificate getCertificate();
method public int getContentHeight();
+ method public static android.content.pm.PackageInfo getCurrentWebViewPackage();
method public android.graphics.Bitmap getFavicon();
method public android.webkit.WebView.HitTestResult getHitTestResult();
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
@@ -45931,6 +46103,8 @@
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
method public java.lang.String getUrl();
+ method public android.webkit.WebChromeClient getWebChromeClient();
+ method public android.webkit.WebViewClient getWebViewClient();
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
@@ -47558,6 +47732,7 @@
method public android.graphics.PorterDuff.Mode getIndeterminateTintMode();
method public android.view.animation.Interpolator getInterpolator();
method public synchronized int getMax();
+ method public synchronized int getMin();
method public synchronized int getProgress();
method public android.content.res.ColorStateList getProgressBackgroundTintList();
method public android.graphics.PorterDuff.Mode getProgressBackgroundTintMode();
@@ -47580,6 +47755,7 @@
method public void setInterpolator(android.content.Context, int);
method public void setInterpolator(android.view.animation.Interpolator);
method public synchronized void setMax(int);
+ method public synchronized void setMin(int);
method public synchronized void setProgress(int);
method public void setProgress(int, boolean);
method public void setProgressBackgroundTintList(android.content.res.ColorStateList);
@@ -52172,6 +52348,8 @@
enum_constant public static final java.lang.annotation.ElementType PACKAGE;
enum_constant public static final java.lang.annotation.ElementType PARAMETER;
enum_constant public static final java.lang.annotation.ElementType TYPE;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_PARAMETER;
+ enum_constant public static final java.lang.annotation.ElementType TYPE_USE;
}
public class IncompleteAnnotationException extends java.lang.RuntimeException {
@@ -52253,7 +52431,7 @@
method public abstract <T extends java.lang.annotation.Annotation> T getAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getAnnotationsByType(java.lang.Class<T>);
- method public default <T extends java.lang.annotation.Annotation> java.lang.annotation.Annotation getDeclaredAnnotation(java.lang.Class<T>);
+ method public default <T extends java.lang.annotation.Annotation> T getDeclaredAnnotation(java.lang.Class<T>);
method public abstract java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public default <T extends java.lang.annotation.Annotation> T[] getDeclaredAnnotationsByType(java.lang.Class<T>);
method public default boolean isAnnotationPresent(java.lang.Class<? extends java.lang.annotation.Annotation>);
@@ -53378,8 +53556,6 @@
method public java.net.URLConnection openConnection(java.net.Proxy) throws java.io.IOException;
method public final java.io.InputStream openStream() throws java.io.IOException;
method public boolean sameFile(java.net.URL);
- method protected void set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String);
- method protected void set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory);
method public java.lang.String toExternalForm();
method public java.net.URI toURI() throws java.net.URISyntaxException;
@@ -59522,8 +59698,10 @@
method public java.util.Set<java.lang.String> getUnicodeLocaleKeys();
method public java.lang.String getUnicodeLocaleType(java.lang.String);
method public java.lang.String getVariant();
+ method public boolean hasExtensions();
method public static synchronized void setDefault(java.util.Locale);
method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale);
+ method public java.util.Locale stripExtensions();
method public java.lang.String toLanguageTag();
method public final java.lang.String toString();
field public static final java.util.Locale CANADA;
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 91334bd..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;
@@ -26,7 +24,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
import android.app.IActivityContainer;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -46,7 +43,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -55,15 +51,17 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.ArrayMap;
+import android.util.Log;
import android.view.IWindowManager;
import com.android.internal.os.BaseCommand;
@@ -72,6 +70,7 @@
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -96,6 +95,7 @@
// Amount we reduce the stack size by when testing a task re-size.
private static final int STACK_BOUNDS_INSET = 10;
+ public static final String NO_CLASS_ERROR_CODE = "Error type 3";
private IActivityManager mAm;
private IPackageManager mPm;
@@ -198,7 +198,7 @@
" --track-allocation: enable tracking of object allocations\n" +
" --user <USER_ID> | current: Specify which user to run as; if not\n" +
" specified then run as the current user.\n" +
- " --stack <STACK_ID>: Specify into which stack should the activity be put." +
+ " --stack <STACK_ID>: Specify into which stack should the activity be put.\n" +
"\n" +
"am startservice: start a Service. Options are:\n" +
" --user <USER_ID> | current: Specify which user to run as; if not\n" +
@@ -233,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" +
@@ -384,82 +385,12 @@
String op = nextArgRequired();
- if (op.equals("start")) {
- runStart();
- } else if (op.equals("startservice")) {
- runStartService();
- } else if (op.equals("stopservice")) {
- runStopService();
- } else if (op.equals("force-stop")) {
- runForceStop();
- } else if (op.equals("kill")) {
- runKill();
- } else if (op.equals("kill-all")) {
- runKillAll();
+ if (op.equals("broadcast")) {
+ sendBroadcast();
} else if (op.equals("instrument")) {
runInstrument();
- } else if (op.equals("trace-ipc")) {
- runTraceIpc();
- } else if (op.equals("broadcast")) {
- sendBroadcast();
- } else if (op.equals("profile")) {
- runProfile();
- } else if (op.equals("dumpheap")) {
- runDumpHeap();
- } else if (op.equals("set-debug-app")) {
- runSetDebugApp();
- } else if (op.equals("clear-debug-app")) {
- runClearDebugApp();
- } else if (op.equals("set-watch-heap")) {
- runSetWatchHeap();
- } else if (op.equals("clear-watch-heap")) {
- runClearWatchHeap();
- } else if (op.equals("bug-report")) {
- runBugReport();
- } else if (op.equals("monitor")) {
- runMonitor();
- } else if (op.equals("hang")) {
- runHang();
- } else if (op.equals("restart")) {
- runRestart();
- } else if (op.equals("idle-maintenance")) {
- runIdleMaintenance();
- } else if (op.equals("screen-compat")) {
- runScreenCompat();
- } else if (op.equals("package-importance")) {
- runPackageImportance();
- } else if (op.equals("to-uri")) {
- runToUri(0);
- } else if (op.equals("to-intent-uri")) {
- runToUri(Intent.URI_INTENT_SCHEME);
- } else if (op.equals("to-app-uri")) {
- runToUri(Intent.URI_ANDROID_APP_SCHEME);
- } else if (op.equals("switch-user")) {
- runSwitchUser();
- } else if (op.equals("start-user")) {
- runStartUserInBackground();
- } else if (op.equals("unlock-user")) {
- runUnlockUser();
- } else if (op.equals("stop-user")) {
- runStopUser();
- } else if (op.equals("stack")) {
- runStack();
- } else if (op.equals("task")) {
- runTask();
- } else if (op.equals("get-config")) {
- runGetConfig();
- } else if (op.equals("suppress-resize-config-changes")) {
- runSuppressResizeConfigChanges();
- } else if (op.equals("set-inactive")) {
- runSetInactive();
- } else if (op.equals("get-inactive")) {
- runGetInactive();
- } else if (op.equals("send-trim-memory")) {
- runSendTrimMemory();
- } else if (op.equals("get-current-user")) {
- runGetCurrentUser();
} else {
- showError("Error: unknown command '" + op + "'");
+ runAmCmd(getRawArgs());
}
}
@@ -475,6 +406,58 @@
return userId;
}
+ static final class MyShellCallback extends ShellCallback {
+ boolean mActive = true;
+
+ @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ if (!mActive) {
+ System.err.println("Open attempt after active for: " + path);
+ return null;
+ }
+ File file = new File(path);
+ //System.err.println("Opening file: " + file.getAbsolutePath());
+ //Log.i("Am", "Opening file: " + file.getAbsolutePath());
+ final ParcelFileDescriptor fd;
+ try {
+ fd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_WRITE_ONLY);
+ } catch (FileNotFoundException e) {
+ String msg = "Unable to open file " + path + ": " + e;
+ System.err.println(msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (seLinuxContext != null) {
+ final String tcon = SELinux.getFileContext(file.getAbsolutePath());
+ if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ String msg = "System server has no access to file context " + tcon;
+ System.err.println(msg + " (from path " + file.getAbsolutePath()
+ + ", context " + seLinuxContext + ")");
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ return fd;
+ }
+ }
+
+ void runAmCmd(String[] args) throws AndroidException {
+ final MyShellCallback cb = new MyShellCallback();
+ try {
+ mAm.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ args, cb, new ResultReceiver(null) { });
+ } catch (RemoteException e) {
+ System.err.println(NO_SYSTEM_ERROR_CODE);
+ throw new AndroidException("Can't call activity manager; is the system running?");
+ } finally {
+ cb.mActive = false;
+ }
+ }
+
private Intent makeIntent(int defUser) throws URISyntaxException {
mStartFlags = 0;
mWaitOption = false;
@@ -510,7 +493,7 @@
} else if (opt.equals("--track-allocation")) {
mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
} else if (opt.equals("--user")) {
- mUserId = parseUserArg(nextArgRequired());
+ mUserId = UserHandle.parseUserArg(nextArgRequired());
} else if (opt.equals("--receiver-permission")) {
mReceiverPermission = nextArgRequired();
} else if (opt.equals("--stack")) {
@@ -523,1126 +506,6 @@
});
}
- private void runStartService() throws Exception {
- Intent intent = makeIntent(UserHandle.USER_CURRENT);
- if (mUserId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't start activity with user 'all'");
- return;
- }
- System.out.println("Starting service: " + intent);
- ComponentName cn = mAm.startService(null, intent, intent.getType(),
- SHELL_PACKAGE_NAME, mUserId);
- if (cn == null) {
- System.err.println("Error: Not found; no service started.");
- } else if (cn.getPackageName().equals("!")) {
- System.err.println("Error: Requires permission " + cn.getClassName());
- } else if (cn.getPackageName().equals("!!")) {
- System.err.println("Error: " + cn.getClassName());
- }
- }
-
- private void runStopService() throws Exception {
- Intent intent = makeIntent(UserHandle.USER_CURRENT);
- if (mUserId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't stop activity with user 'all'");
- return;
- }
- System.out.println("Stopping service: " + intent);
- int result = mAm.stopService(null, intent, intent.getType(), mUserId);
- if (result == 0) {
- System.err.println("Service not stopped: was not running.");
- } else if (result == 1) {
- System.err.println("Service stopped");
- } else if (result == -1) {
- System.err.println("Error stopping service");
- }
- }
-
- private void runStart() throws Exception {
- Intent intent = makeIntent(UserHandle.USER_CURRENT);
-
- if (mUserId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't start service with user 'all'");
- return;
- }
-
- String mimeType = intent.getType();
- if (mimeType == null && intent.getData() != null
- && "content".equals(intent.getData().getScheme())) {
- mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
- }
-
-
- do {
- if (mStopOption) {
- String packageName;
- if (intent.getComponent() != null) {
- packageName = intent.getComponent().getPackageName();
- } else {
- List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
- mUserId).getList();
- if (activities == null || activities.size() <= 0) {
- System.err.println("Error: Intent does not match any activities: "
- + intent);
- return;
- } else if (activities.size() > 1) {
- System.err.println("Error: Intent matches multiple activities; can't stop: "
- + intent);
- return;
- }
- packageName = activities.get(0).activityInfo.packageName;
- }
- System.out.println("Stopping: " + packageName);
- mAm.forceStopPackage(packageName, mUserId);
- Thread.sleep(250);
- }
-
- System.out.println("Starting: " + intent);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- ParcelFileDescriptor fd = null;
- ProfilerInfo profilerInfo = null;
-
- if (mProfileFile != null) {
- try {
- fd = openForSystemServer(
- new File(mProfileFile),
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- System.err.println("Error: Unable to open file: " + mProfileFile);
- System.err.println("Consider using a file under /data/local/tmp/");
- return;
- }
- profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
- }
-
- IActivityManager.WaitResult result = null;
- int res;
- final long startTime = SystemClock.uptimeMillis();
- ActivityOptions options = null;
- if (mStackId != INVALID_STACK_ID) {
- options = ActivityOptions.makeBasic();
- options.setLaunchStackId(mStackId);
- }
- if (mWaitOption) {
- result = mAm.startActivityAndWait(null, null, intent, mimeType,
- null, null, 0, mStartFlags, profilerInfo,
- options != null ? options.toBundle() : null, mUserId);
- res = result.result;
- } else {
- res = mAm.startActivityAsUser(null, null, intent, mimeType,
- null, null, 0, mStartFlags, profilerInfo,
- options != null ? options.toBundle() : null, mUserId);
- }
- final long endTime = SystemClock.uptimeMillis();
- PrintStream out = mWaitOption ? System.out : System.err;
- boolean launched = false;
- switch (res) {
- case ActivityManager.START_SUCCESS:
- launched = true;
- break;
- case ActivityManager.START_SWITCHES_CANCELED:
- launched = true;
- out.println(
- "Warning: Activity not started because the "
- + " current activity is being kept for the user.");
- break;
- case ActivityManager.START_DELIVERED_TO_TOP:
- launched = true;
- out.println(
- "Warning: Activity not started, intent has "
- + "been delivered to currently running "
- + "top-most instance.");
- break;
- case ActivityManager.START_RETURN_INTENT_TO_CALLER:
- launched = true;
- out.println(
- "Warning: Activity not started because intent "
- + "should be handled by the caller");
- break;
- case ActivityManager.START_TASK_TO_FRONT:
- launched = true;
- out.println(
- "Warning: Activity not started, its current "
- + "task has been brought to the front");
- break;
- case ActivityManager.START_INTENT_NOT_RESOLVED:
- out.println(
- "Error: Activity not started, unable to "
- + "resolve " + intent.toString());
- break;
- case ActivityManager.START_CLASS_NOT_FOUND:
- out.println(NO_CLASS_ERROR_CODE);
- out.println("Error: Activity class " +
- intent.getComponent().toShortString()
- + " does not exist.");
- break;
- case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
- out.println(
- "Error: Activity not started, you requested to "
- + "both forward and receive its result");
- break;
- case ActivityManager.START_PERMISSION_DENIED:
- out.println(
- "Error: Activity not started, you do not "
- + "have permission to access it.");
- break;
- case ActivityManager.START_NOT_VOICE_COMPATIBLE:
- out.println(
- "Error: Activity not started, voice control not allowed for: "
- + intent);
- break;
- case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
- out.println(
- "Error: Not allowed to start background user activity"
- + " that shouldn't be displayed for all users.");
- break;
- default:
- out.println(
- "Error: Activity not started, unknown error code " + res);
- break;
- }
- if (mWaitOption && launched) {
- if (result == null) {
- result = new IActivityManager.WaitResult();
- result.who = intent.getComponent();
- }
- System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
- if (result.who != null) {
- System.out.println("Activity: " + result.who.flattenToShortString());
- }
- if (result.thisTime >= 0) {
- System.out.println("ThisTime: " + result.thisTime);
- }
- if (result.totalTime >= 0) {
- System.out.println("TotalTime: " + result.totalTime);
- }
- System.out.println("WaitTime: " + (endTime-startTime));
- System.out.println("Complete");
- }
- mRepeat--;
- if (mRepeat > 0) {
- mAm.unhandledBack();
- }
- } while (mRepeat > 0);
- }
-
- private void runForceStop() throws Exception {
- int userId = UserHandle.USER_ALL;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- mAm.forceStopPackage(nextArgRequired(), userId);
- }
-
- private void runKill() throws Exception {
- int userId = UserHandle.USER_ALL;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- mAm.killBackgroundProcesses(nextArgRequired(), userId);
- }
-
- private void runKillAll() throws Exception {
- mAm.killAllBackgroundProcesses();
- }
-
- private void sendBroadcast() throws Exception {
- Intent intent = makeIntent(UserHandle.USER_CURRENT);
- IntentReceiver receiver = new IntentReceiver();
- String[] requiredPermissions = mReceiverPermission == null ? null
- : new String[] {mReceiverPermission};
- System.out.println("Broadcasting: " + intent);
- mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, requiredPermissions,
- android.app.AppOpsManager.OP_NONE, null, true, false, mUserId);
- 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;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-p")) {
- profileFile = nextArgRequired();
- } else if (opt.equals("-w")) {
- wait = true;
- } else if (opt.equals("-r")) {
- rawMode = true;
- } else if (opt.equals("-e")) {
- argKey = nextArgRequired();
- argValue = nextArgRequired();
- args.putString(argKey, argValue);
- } else if (opt.equals("--no_window_animation")
- || opt.equals("--no-window-animation")) {
- no_window_animation = true;
- } else if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else if (opt.equals("--abi")) {
- abi = nextArgRequired();
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
-
- if (userId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't start instrumentation with user 'all'");
- return;
- }
-
- String cnArg = 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 void runTraceIpc() throws Exception {
- String op = nextArgRequired();
- if (op.equals("start")) {
- runTraceIpcStart();
- } else if (op.equals("stop")) {
- runTraceIpcStop();
- } else {
- showError("Error: unknown command '" + op + "'");
- return;
- }
- }
-
- private void runTraceIpcStart() throws Exception {
- System.out.println("Starting IPC tracing.");
- mAm.startBinderTracking();
- }
-
- private void runTraceIpcStop() throws Exception {
- String opt;
- String filename = null;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--dump-file")) {
- filename = nextArgRequired();
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- if (filename == null) {
- System.err.println("Error: Specify filename to dump logs to.");
- return;
- }
-
- ParcelFileDescriptor fd = null;
-
- try {
- File file = new File(filename);
- file.delete();
- fd = openForSystemServer(file,
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- System.err.println("Error: Unable to open file: " + filename);
- System.err.println("Consider using a file under /data/local/tmp/");
- return;
- }
-
- ;
- if (!mAm.stopBinderTrackingAndDump(fd)) {
- throw new AndroidException("STOP TRACE FAILED.");
- }
-
- System.out.println("Stopped IPC tracing. Dumping logs to: " + filename);
- }
-
- static void removeWallOption() {
- String props = SystemProperties.get("dalvik.vm.extra-opts");
- if (props != null && props.contains("-Xprofile:wallclock")) {
- props = props.replace("-Xprofile:wallclock", "");
- props = props.trim();
- SystemProperties.set("dalvik.vm.extra-opts", props);
- }
- }
-
- private void runProfile() throws Exception {
- String profileFile = null;
- boolean start = false;
- boolean wall = false;
- int userId = UserHandle.USER_CURRENT;
- int profileType = 0;
- mSamplingInterval = 0;
-
- String process = null;
-
- String cmd = nextArgRequired();
-
- if ("start".equals(cmd)) {
- start = true;
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else if (opt.equals("--wall")) {
- wall = true;
- } else if (opt.equals("--sampling")) {
- mSamplingInterval = Integer.parseInt(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- process = nextArgRequired();
- } else if ("stop".equals(cmd)) {
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- process = nextArg();
- } else {
- // Compatibility with old syntax: process is specified first.
- process = cmd;
- cmd = nextArgRequired();
- if ("start".equals(cmd)) {
- start = true;
- } else if (!"stop".equals(cmd)) {
- throw new IllegalArgumentException("Profile command " + process + " not valid");
- }
- }
-
- if (userId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't profile with user 'all'");
- return;
- }
-
- ParcelFileDescriptor fd = null;
- ProfilerInfo profilerInfo = null;
-
- if (start) {
- profileFile = nextArgRequired();
- try {
- fd = openForSystemServer(
- new File(profileFile),
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- System.err.println("Error: Unable to open file: " + profileFile);
- System.err.println("Consider using a file under /data/local/tmp/");
- return;
- }
- profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false);
- }
-
- try {
- if (wall) {
- // XXX doesn't work -- this needs to be set before booting.
- String props = SystemProperties.get("dalvik.vm.extra-opts");
- if (props == null || !props.contains("-Xprofile:wallclock")) {
- props = props + " -Xprofile:wallclock";
- //SystemProperties.set("dalvik.vm.extra-opts", props);
- }
- } else if (start) {
- //removeWallOption();
- }
- if (!mAm.profileControl(process, userId, start, profilerInfo, profileType)) {
- wall = false;
- throw new AndroidException("PROFILE FAILED on process " + process);
- }
- } finally {
- if (!wall) {
- //removeWallOption();
- }
- }
- }
-
- private void runDumpHeap() throws Exception {
- boolean managed = true;
- int userId = UserHandle.USER_CURRENT;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- if (userId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't dump heap with user 'all'");
- return;
- }
- } else if (opt.equals("-n")) {
- managed = false;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- String process = nextArgRequired();
- String heapFile = nextArgRequired();
- ParcelFileDescriptor fd = null;
-
- try {
- File file = new File(heapFile);
- file.delete();
- fd = openForSystemServer(file,
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- System.err.println("Error: Unable to open file: " + heapFile);
- System.err.println("Consider using a file under /data/local/tmp/");
- return;
- }
-
- if (!mAm.dumpHeap(process, userId, managed, heapFile, fd)) {
- throw new AndroidException("HEAP DUMP FAILED on process " + process);
- }
- }
-
- private void runSetDebugApp() throws Exception {
- boolean wait = false;
- boolean persistent = false;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-w")) {
- wait = true;
- } else if (opt.equals("--persistent")) {
- persistent = true;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
-
- String pkg = nextArgRequired();
- mAm.setDebugApp(pkg, wait, persistent);
- }
-
- private void runClearDebugApp() throws Exception {
- mAm.setDebugApp(null, false, true);
- }
-
- private void runSetWatchHeap() throws Exception {
- String proc = nextArgRequired();
- String limit = nextArgRequired();
- mAm.setDumpHeapDebugLimit(proc, 0, Long.parseLong(limit), null);
- }
-
- private void runClearWatchHeap() throws Exception {
- String proc = nextArgRequired();
- mAm.setDumpHeapDebugLimit(proc, 0, -1, null);
- }
-
- private void runBugReport() throws Exception {
- String opt;
- int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--progress")) {
- bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- mAm.requestBugReport(bugreportType);
- System.out.println("Your lovely bug report is being created; please be patient.");
- }
-
- private void runSwitchUser() throws Exception {
- String user = nextArgRequired();
- mAm.switchUser(Integer.parseInt(user));
- }
-
- private void runStartUserInBackground() throws Exception {
- String user = nextArgRequired();
- boolean success = mAm.startUserInBackground(Integer.parseInt(user));
- if (success) {
- System.out.println("Success: user started");
- } else {
- System.err.println("Error: could not start user");
- }
- }
-
- private byte[] argToBytes(String arg) {
- if (arg.equals("!")) {
- return null;
- } else {
- return HexDump.hexStringToByteArray(arg);
- }
- }
-
- private void runUnlockUser() throws Exception {
- int userId = Integer.parseInt(nextArgRequired());
- byte[] token = argToBytes(nextArgRequired());
- byte[] secret = argToBytes(nextArgRequired());
- boolean success = mAm.unlockUser(userId, token, secret, null);
- if (success) {
- System.out.println("Success: user unlocked");
- } else {
- System.err.println("Error: could not unlock user");
- }
- }
-
- private static class StopUserCallback extends IStopUserCallback.Stub {
- private boolean mFinished = false;
-
- public synchronized void waitForFinish() {
- try {
- while (!mFinished) wait();
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public synchronized void userStopped(int userId) {
- mFinished = true;
- notifyAll();
- }
-
- @Override
- public synchronized void userStopAborted(int userId) {
- mFinished = true;
- notifyAll();
- }
- }
-
- private void runStopUser() throws Exception {
- boolean wait = false;
- boolean force = false;
- String opt;
- while ((opt = nextOption()) != null) {
- if ("-w".equals(opt)) {
- wait = true;
- } else if ("-f".equals(opt)) {
- force = true;
- } else {
- System.err.println("Error: unknown option: " + opt);
- return;
- }
- }
- int user = Integer.parseInt(nextArgRequired());
- StopUserCallback callback = wait ? new StopUserCallback() : null;
-
- int res = mAm.stopUser(user, force, callback);
- if (res != ActivityManager.USER_OP_SUCCESS) {
- String txt = "";
- switch (res) {
- case ActivityManager.USER_OP_IS_CURRENT:
- txt = " (Can't stop current user)";
- break;
- case ActivityManager.USER_OP_UNKNOWN_USER:
- txt = " (Unknown user " + user + ")";
- break;
- case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
- txt = " (System user cannot be stopped)";
- break;
- case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP:
- txt = " (Can't stop user " + user
- + " - one of its related users can't be stopped)";
- break;
- }
- System.err.println("Switch failed: " + res + txt);
- } else if (callback != null) {
- callback.waitForFinish();
- }
- }
-
- class MyActivityController extends IActivityController.Stub {
- final String mGdbPort;
- final boolean mMonkey;
-
- static final int STATE_NORMAL = 0;
- static final int STATE_CRASHED = 1;
- static final int STATE_EARLY_ANR = 2;
- static final int STATE_ANR = 3;
-
- int mState;
-
- static final int RESULT_DEFAULT = 0;
-
- static final int RESULT_CRASH_DIALOG = 0;
- static final int RESULT_CRASH_KILL = 1;
-
- static final int RESULT_EARLY_ANR_CONTINUE = 0;
- static final int RESULT_EARLY_ANR_KILL = 1;
-
- static final int RESULT_ANR_DIALOG = 0;
- static final int RESULT_ANR_KILL = 1;
- static final int RESULT_ANR_WAIT = 1;
-
- int mResult;
-
- Process mGdbProcess;
- Thread mGdbThread;
- boolean mGotGdbPrint;
-
- MyActivityController(String gdbPort, boolean monkey) {
- mGdbPort = gdbPort;
- mMonkey = monkey;
- }
-
- @Override
- public boolean activityResuming(String pkg) {
- synchronized (this) {
- System.out.println("** Activity resuming: " + pkg);
- }
- return true;
- }
-
- @Override
- public boolean activityStarting(Intent intent, String pkg) {
- synchronized (this) {
- System.out.println("** Activity starting: " + pkg);
- }
- return true;
- }
-
- @Override
- public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
- long timeMillis, String stackTrace) {
- synchronized (this) {
- System.out.println("** ERROR: PROCESS CRASHED");
- System.out.println("processName: " + processName);
- System.out.println("processPid: " + pid);
- System.out.println("shortMsg: " + shortMsg);
- System.out.println("longMsg: " + longMsg);
- System.out.println("timeMillis: " + timeMillis);
- System.out.println("stack:");
- System.out.print(stackTrace);
- System.out.println("#");
- int result = waitControllerLocked(pid, STATE_CRASHED);
- return result == RESULT_CRASH_KILL ? false : true;
- }
- }
-
- @Override
- public int appEarlyNotResponding(String processName, int pid, String annotation) {
- synchronized (this) {
- System.out.println("** ERROR: EARLY PROCESS NOT RESPONDING");
- System.out.println("processName: " + processName);
- System.out.println("processPid: " + pid);
- System.out.println("annotation: " + annotation);
- int result = waitControllerLocked(pid, STATE_EARLY_ANR);
- if (result == RESULT_EARLY_ANR_KILL) return -1;
- return 0;
- }
- }
-
- @Override
- public int appNotResponding(String processName, int pid, String processStats) {
- synchronized (this) {
- System.out.println("** ERROR: PROCESS NOT RESPONDING");
- System.out.println("processName: " + processName);
- System.out.println("processPid: " + pid);
- System.out.println("processStats:");
- System.out.print(processStats);
- System.out.println("#");
- int result = waitControllerLocked(pid, STATE_ANR);
- if (result == RESULT_ANR_KILL) return -1;
- if (result == RESULT_ANR_WAIT) return 1;
- return 0;
- }
- }
-
- @Override
- public int systemNotResponding(String message) {
- synchronized (this) {
- System.out.println("** ERROR: PROCESS NOT RESPONDING");
- System.out.println("message: " + message);
- System.out.println("#");
- System.out.println("Allowing system to die.");
- return -1;
- }
- }
-
- void killGdbLocked() {
- mGotGdbPrint = false;
- if (mGdbProcess != null) {
- System.out.println("Stopping gdbserver");
- mGdbProcess.destroy();
- mGdbProcess = null;
- }
- if (mGdbThread != null) {
- mGdbThread.interrupt();
- mGdbThread = null;
- }
- }
-
- int waitControllerLocked(int pid, int state) {
- if (mGdbPort != null) {
- killGdbLocked();
-
- try {
- System.out.println("Starting gdbserver on port " + mGdbPort);
- System.out.println("Do the following:");
- System.out.println(" adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort);
- System.out.println(" gdbclient app_process :" + mGdbPort);
-
- mGdbProcess = Runtime.getRuntime().exec(new String[] {
- "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid)
- });
- final InputStreamReader converter = new InputStreamReader(
- mGdbProcess.getInputStream());
- mGdbThread = new Thread() {
- @Override
- public void run() {
- BufferedReader in = new BufferedReader(converter);
- String line;
- int count = 0;
- while (true) {
- synchronized (MyActivityController.this) {
- if (mGdbThread == null) {
- return;
- }
- if (count == 2) {
- mGotGdbPrint = true;
- MyActivityController.this.notifyAll();
- }
- }
- try {
- line = in.readLine();
- if (line == null) {
- return;
- }
- System.out.println("GDB: " + line);
- count++;
- } catch (IOException e) {
- return;
- }
- }
- }
- };
- mGdbThread.start();
-
- // Stupid waiting for .5s. Doesn't matter if we end early.
- try {
- this.wait(500);
- } catch (InterruptedException e) {
- }
-
- } catch (IOException e) {
- System.err.println("Failure starting gdbserver: " + e);
- killGdbLocked();
- }
- }
- mState = state;
- System.out.println("");
- printMessageForState();
-
- while (mState != STATE_NORMAL) {
- try {
- wait();
- } catch (InterruptedException e) {
- }
- }
-
- killGdbLocked();
-
- return mResult;
- }
-
- void resumeController(int result) {
- synchronized (this) {
- mState = STATE_NORMAL;
- mResult = result;
- notifyAll();
- }
- }
-
- void printMessageForState() {
- switch (mState) {
- case STATE_NORMAL:
- System.out.println("Monitoring activity manager... available commands:");
- break;
- case STATE_CRASHED:
- System.out.println("Waiting after crash... available commands:");
- System.out.println("(c)ontinue: show crash dialog");
- System.out.println("(k)ill: immediately kill app");
- break;
- case STATE_EARLY_ANR:
- System.out.println("Waiting after early ANR... available commands:");
- System.out.println("(c)ontinue: standard ANR processing");
- System.out.println("(k)ill: immediately kill app");
- break;
- case STATE_ANR:
- System.out.println("Waiting after ANR... available commands:");
- System.out.println("(c)ontinue: show ANR dialog");
- System.out.println("(k)ill: immediately kill app");
- System.out.println("(w)ait: wait some more");
- break;
- }
- System.out.println("(q)uit: finish monitoring");
- }
-
- void run() throws RemoteException {
- try {
- printMessageForState();
-
- mAm.setActivityController(this, mMonkey);
- mState = STATE_NORMAL;
-
- InputStreamReader converter = new InputStreamReader(System.in);
- BufferedReader in = new BufferedReader(converter);
- String line;
-
- while ((line = in.readLine()) != null) {
- boolean addNewline = true;
- if (line.length() <= 0) {
- addNewline = false;
- } else if ("q".equals(line) || "quit".equals(line)) {
- resumeController(RESULT_DEFAULT);
- break;
- } else if (mState == STATE_CRASHED) {
- if ("c".equals(line) || "continue".equals(line)) {
- resumeController(RESULT_CRASH_DIALOG);
- } else if ("k".equals(line) || "kill".equals(line)) {
- resumeController(RESULT_CRASH_KILL);
- } else {
- System.out.println("Invalid command: " + line);
- }
- } else if (mState == STATE_ANR) {
- if ("c".equals(line) || "continue".equals(line)) {
- resumeController(RESULT_ANR_DIALOG);
- } else if ("k".equals(line) || "kill".equals(line)) {
- resumeController(RESULT_ANR_KILL);
- } else if ("w".equals(line) || "wait".equals(line)) {
- resumeController(RESULT_ANR_WAIT);
- } else {
- System.out.println("Invalid command: " + line);
- }
- } else if (mState == STATE_EARLY_ANR) {
- if ("c".equals(line) || "continue".equals(line)) {
- resumeController(RESULT_EARLY_ANR_CONTINUE);
- } else if ("k".equals(line) || "kill".equals(line)) {
- resumeController(RESULT_EARLY_ANR_KILL);
- } else {
- System.out.println("Invalid command: " + line);
- }
- } else {
- System.out.println("Invalid command: " + line);
- }
-
- synchronized (this) {
- if (addNewline) {
- System.out.println("");
- }
- printMessageForState();
- }
- }
-
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mAm.setActivityController(null, mMonkey);
- }
- }
- }
-
- private void runMonitor() throws Exception {
- String opt;
- String gdbPort = null;
- boolean monkey = false;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--gdb")) {
- gdbPort = nextArgRequired();
- } else if (opt.equals("-m")) {
- monkey = true;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
-
- MyActivityController controller = new MyActivityController(gdbPort, monkey);
- controller.run();
- }
-
- private void runHang() throws Exception {
- String opt;
- boolean allowRestart = false;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--allow-restart")) {
- allowRestart = true;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
-
- System.out.println("Hanging the system...");
- mAm.hang(new Binder(), allowRestart);
- }
-
- private void runRestart() throws Exception {
- String opt;
- while ((opt=nextOption()) != null) {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
-
- System.out.println("Restart the system...");
- mAm.restart();
- }
-
- private void runIdleMaintenance() throws Exception {
- String opt;
- while ((opt=nextOption()) != null) {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
-
- System.out.println("Performing idle maintenance...");
- try {
- mAm.sendIdleJobTrigger();
- } catch (RemoteException e) {
- }
- }
-
- private void runScreenCompat() throws Exception {
- String mode = nextArgRequired();
- boolean enabled;
- if ("on".equals(mode)) {
- enabled = true;
- } else if ("off".equals(mode)) {
- enabled = false;
- } else {
- System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode);
- return;
- }
-
- String packageName = nextArgRequired();
- do {
- try {
- mAm.setPackageScreenCompatMode(packageName, enabled
- ? ActivityManager.COMPAT_MODE_ENABLED
- : ActivityManager.COMPAT_MODE_DISABLED);
- } catch (RemoteException e) {
- }
- packageName = nextArg();
- } while (packageName != null);
- }
-
- private void runPackageImportance() throws Exception {
- String packageName = nextArgRequired();
- try {
- int procState = mAm.getPackageProcessState(packageName, "com.android.shell");
- System.out.println(
- ActivityManager.RunningAppProcessInfo.procStateToImportance(procState));
- } catch (RemoteException e) {
- }
- }
-
- private void runToUri(int flags) throws Exception {
- Intent intent = makeIntent(UserHandle.USER_CURRENT);
- System.out.println(intent.toUri(flags));
- }
-
private class IntentReceiver extends IIntentReceiver.Stub {
private boolean mFinished = false;
@@ -1654,8 +517,8 @@
if (extras != null) line = line + ", extras: " + extras;
System.out.println(line);
synchronized (this) {
- mFinished = true;
- notifyAll();
+ mFinished = true;
+ notifyAll();
}
}
@@ -1668,910 +531,54 @@
}
}
- 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;
- }
- }
-
- private void runStack() throws Exception {
- String op = nextArgRequired();
- switch (op) {
- case "start":
- runStackStart();
- break;
- case "movetask":
- runStackMoveTask();
- break;
- case "resize":
- runStackResize();
- break;
- case "resize-animated":
- runStackResizeAnimated();
- break;
- case "resize-docked-stack":
- runStackResizeDocked();
- break;
- case "positiontask":
- runStackPositionTask();
- break;
- case "list":
- runStackList();
- break;
- case "info":
- runStackInfo();
- break;
- case "move-top-activity-to-pinned-stack":
- runMoveTopActivityToPinnedStack();
- break;
- case "size-docked-stack-test":
- runStackSizeDockedStackTest();
- break;
- case "remove":
- runStackRemove();
- break;
- default:
- showError("Error: unknown command '" + op + "'");
- break;
- }
- }
-
- private void runStackStart() throws Exception {
- String displayIdStr = nextArgRequired();
- int displayId = Integer.parseInt(displayIdStr);
+ private void sendBroadcast() throws Exception {
Intent intent = makeIntent(UserHandle.USER_CURRENT);
-
- try {
- IActivityContainer container = mAm.createStackOnDisplay(displayId);
- if (container != null) {
- container.startActivity(intent);
- }
- } catch (RemoteException e) {
- }
+ IntentReceiver receiver = new IntentReceiver();
+ String[] requiredPermissions = mReceiverPermission == null ? null
+ : new String[] {mReceiverPermission};
+ System.out.println("Broadcasting: " + intent);
+ mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, requiredPermissions,
+ android.app.AppOpsManager.OP_NONE, null, true, false, mUserId);
+ receiver.waitForFinish();
}
- private void runStackMoveTask() throws Exception {
- String taskIdStr = nextArgRequired();
- int taskId = Integer.parseInt(taskIdStr);
- String stackIdStr = nextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- String toTopStr = nextArgRequired();
- final boolean toTop;
- if ("true".equals(toTopStr)) {
- toTop = true;
- } else if ("false".equals(toTopStr)) {
- toTop = false;
- } else {
- System.err.println("Error: bad toTop arg: " + toTopStr);
- return;
- }
-
- try {
- mAm.moveTaskToStack(taskId, stackId, toTop);
- } catch (RemoteException e) {
- }
- }
-
- private void runStackResize() throws Exception {
- String stackIdStr = nextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- final Rect bounds = getBounds();
- if (bounds == null) {
- System.err.println("Error: invalid input bounds");
- return;
- }
- resizeStack(stackId, bounds, 0);
- }
-
- private void runStackResizeAnimated() throws Exception {
- String stackIdStr = nextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- final Rect bounds;
- if ("null".equals(mArgs.peekNextArg())) {
- bounds = null;
- } else {
- bounds = getBounds();
- if (bounds == null) {
- System.err.println("Error: invalid input bounds");
- return;
- }
- }
- resizeStackUnchecked(stackId, bounds, 0, true);
- }
-
- private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) {
- try {
- mAm.resizeStack(stackId, bounds, false, false, animate, -1);
- Thread.sleep(delayMs);
- } catch (RemoteException e) {
- showError("Error: resizing stack " + e);
- } catch (InterruptedException e) {
- }
- }
-
- private void runStackResizeDocked() throws Exception {
- final Rect bounds = getBounds();
- final Rect taskBounds = getBounds();
- if (bounds == null || taskBounds == null) {
- System.err.println("Error: invalid input bounds");
- return;
- }
- try {
- mAm.resizeDockedStack(bounds, taskBounds, null, null, null);
- } catch (RemoteException e) {
- showError("Error: resizing docked stack " + e);
- }
- }
-
- private void resizeStack(int stackId, Rect bounds, int delayMs)
- throws Exception {
- if (bounds == null) {
- showError("Error: invalid input bounds");
- return;
- }
- resizeStackUnchecked(stackId, bounds, delayMs, false);
- }
-
- private void runStackPositionTask() throws Exception {
- String taskIdStr = nextArgRequired();
- int taskId = Integer.parseInt(taskIdStr);
- String stackIdStr = nextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- String positionStr = nextArgRequired();
- int position = Integer.parseInt(positionStr);
-
- try {
- mAm.positionTaskInStack(taskId, stackId, position);
- } catch (RemoteException e) {
- }
- }
-
- private void runStackList() throws Exception {
- try {
- List<StackInfo> stacks = mAm.getAllStackInfos();
- for (StackInfo info : stacks) {
- System.out.println(info);
- }
- } catch (RemoteException e) {
- }
- }
-
- private void runStackInfo() throws Exception {
- try {
- String stackIdStr = nextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- StackInfo info = mAm.getStackInfo(stackId);
- System.out.println(info);
- } catch (RemoteException e) {
- }
- }
-
- private void runStackRemove() throws Exception {
- String stackIdStr = nextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- mAm.removeStack(stackId);
- }
-
- private void runMoveTopActivityToPinnedStack() throws Exception {
- int stackId = Integer.parseInt(nextArgRequired());
- final Rect bounds = getBounds();
- if (bounds == null) {
- System.err.println("Error: invalid input bounds");
- return;
- }
-
- try {
- if (!mAm.moveTopActivityToPinnedStack(stackId, bounds)) {
- showError("Didn't move top activity to pinned stack.");
- }
- } catch (RemoteException e) {
- showError("Unable to move top activity: " + e);
- return;
- }
- }
-
- private void runStackSizeDockedStackTest() throws Exception {
- final int stepSize = Integer.parseInt(nextArgRequired());
- final String side = nextArgRequired();
- final String delayStr = nextArg();
- final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-
- Rect bounds;
- try {
- StackInfo info = mAm.getStackInfo(DOCKED_STACK_ID);
- if (info == null) {
- showError("Docked stack doesn't exist");
- return;
- }
- if (info.bounds == null) {
- showError("Docked stack doesn't have a bounds");
- return;
- }
- bounds = info.bounds;
- } catch (RemoteException e) {
- showError("Unable to get docked stack info:" + e);
- return;
- }
-
- final boolean horizontalGrowth = "l".equals(side) || "r".equals(side);
- final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2;
- int currentPoint;
- switch (side) {
- case "l":
- currentPoint = bounds.left;
- break;
- case "r":
- currentPoint = bounds.right;
- break;
- case "t":
- currentPoint = bounds.top;
- break;
- case "b":
- currentPoint = bounds.bottom;
- break;
- default:
- showError("Unknown growth side: " + side);
- return;
- }
-
- final int startPoint = currentPoint;
- final int minPoint = currentPoint - changeSize;
- final int maxPoint = currentPoint + changeSize;
-
- int maxChange;
- System.out.println("Shrinking docked stack side=" + side);
- while (currentPoint > minPoint) {
- maxChange = Math.min(stepSize, currentPoint - minPoint);
- currentPoint -= maxChange;
- setBoundsSide(bounds, side, currentPoint);
- resizeStack(DOCKED_STACK_ID, bounds, delayMs);
- }
-
- System.out.println("Growing docked stack side=" + side);
- while (currentPoint < maxPoint) {
- maxChange = Math.min(stepSize, maxPoint - currentPoint);
- currentPoint += maxChange;
- setBoundsSide(bounds, side, currentPoint);
- resizeStack(DOCKED_STACK_ID, bounds, delayMs);
- }
-
- System.out.println("Back to Original size side=" + side);
- while (currentPoint > startPoint) {
- maxChange = Math.min(stepSize, currentPoint - startPoint);
- currentPoint -= maxChange;
- setBoundsSide(bounds, side, currentPoint);
- resizeStack(DOCKED_STACK_ID, bounds, delayMs);
- }
- }
-
- private void setBoundsSide(Rect bounds, String side, int value) {
- switch (side) {
- case "l":
- bounds.left = value;
- break;
- case "r":
- bounds.right = value;
- break;
- case "t":
- bounds.top = value;
- break;
- case "b":
- bounds.bottom = value;
- break;
- default:
- showError("Unknown set side: " + side);
- break;
- }
- }
-
- private void runTask() throws Exception {
- String op = nextArgRequired();
- if (op.equals("lock")) {
- runTaskLock();
- } else if (op.equals("resizeable")) {
- runTaskResizeable();
- } else if (op.equals("resize")) {
- runTaskResize();
- } else if (op.equals("drag-task-test")) {
- runTaskDragTaskTest();
- } else if (op.equals("size-task-test")) {
- runTaskSizeTaskTest();
- } else {
- showError("Error: unknown command '" + op + "'");
- return;
- }
- }
-
- private void runTaskLock() throws Exception {
- String taskIdStr = nextArgRequired();
- try {
- if (taskIdStr.equals("stop")) {
- mAm.stopLockTaskMode();
- } else {
- int taskId = Integer.parseInt(taskIdStr);
- mAm.startLockTaskMode(taskId);
- }
- System.err.println("Activity manager is " + (mAm.isInLockTaskMode() ? "" : "not ") +
- "in lockTaskMode");
- } catch (RemoteException e) {
- }
- }
-
- private void runTaskResizeable() throws Exception {
- final String taskIdStr = nextArgRequired();
- final int taskId = Integer.parseInt(taskIdStr);
- final String resizeableStr = nextArgRequired();
- final int resizeableMode = Integer.parseInt(resizeableStr);
-
- try {
- mAm.setTaskResizeable(taskId, resizeableMode);
- } catch (RemoteException e) {
- }
- }
-
- private void runTaskResize() throws Exception {
- final String taskIdStr = nextArgRequired();
- final int taskId = Integer.parseInt(taskIdStr);
- final Rect bounds = getBounds();
- if (bounds == null) {
- System.err.println("Error: invalid input bounds");
- return;
- }
- taskResize(taskId, bounds, 0, false);
- }
-
- private void taskResize(int taskId, Rect bounds, int delay_ms, boolean pretendUserResize) {
- try {
- final int resizeMode = pretendUserResize ? RESIZE_MODE_USER : RESIZE_MODE_SYSTEM;
- mAm.resizeTask(taskId, bounds, resizeMode);
- Thread.sleep(delay_ms);
- } catch (RemoteException e) {
- System.err.println("Error changing task bounds: " + e);
- } catch (InterruptedException e) {
- }
- }
-
- private void runTaskDragTaskTest() {
- final int taskId = Integer.parseInt(nextArgRequired());
- final int stepSize = Integer.parseInt(nextArgRequired());
- final String delayStr = nextArg();
- final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
- final StackInfo stackInfo;
- Rect taskBounds;
- try {
- stackInfo = mAm.getStackInfo(mAm.getFocusedStackId());
- taskBounds = mAm.getTaskBounds(taskId);
- } catch (RemoteException e) {
- System.err.println("Error getting focus stack info or task bounds: " + e);
- return;
- }
- final Rect stackBounds = stackInfo.bounds;
- int travelRight = stackBounds.width() - taskBounds.width();
- int travelLeft = -travelRight;
- int travelDown = stackBounds.height() - taskBounds.height();
- int travelUp = -travelDown;
- int passes = 0;
-
- // We do 2 passes to get back to the original location of the task.
- while (passes < 2) {
- // Move right
- System.out.println("Moving right...");
- travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
- System.out.println("Still need to travel right by " + travelRight);
-
- // Move down
- System.out.println("Moving down...");
- travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
- System.out.println("Still need to travel down by " + travelDown);
-
- // Move left
- System.out.println("Moving left...");
- travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
- System.out.println("Still need to travel left by " + travelLeft);
-
- // Move up
- System.out.println("Moving up...");
- travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
- System.out.println("Still need to travel up by " + travelUp);
-
- try {
- taskBounds = mAm.getTaskBounds(taskId);
- } catch (RemoteException e) {
- System.err.println("Error getting task bounds: " + e);
- return;
- }
- passes++;
- }
- }
-
- private int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize,
- int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms) {
- int maxMove;
- if (movingForward) {
- while (maxToTravel > 0
- && ((horizontal && taskRect.right < stackRect.right)
- ||(!horizontal && taskRect.bottom < stackRect.bottom))) {
- if (horizontal) {
- maxMove = Math.min(stepSize, stackRect.right - taskRect.right);
- maxToTravel -= maxMove;
- taskRect.right += maxMove;
- taskRect.left += maxMove;
- } else {
- maxMove = Math.min(stepSize, stackRect.bottom - taskRect.bottom);
- maxToTravel -= maxMove;
- taskRect.top += maxMove;
- taskRect.bottom += maxMove;
- }
- taskResize(taskId, taskRect, delay_ms, false);
- }
- } else {
- while (maxToTravel < 0
- && ((horizontal && taskRect.left > stackRect.left)
- ||(!horizontal && taskRect.top > stackRect.top))) {
- if (horizontal) {
- maxMove = Math.min(stepSize, taskRect.left - stackRect.left);
- maxToTravel -= maxMove;
- taskRect.right -= maxMove;
- taskRect.left -= maxMove;
- } else {
- maxMove = Math.min(stepSize, taskRect.top - stackRect.top);
- maxToTravel -= maxMove;
- taskRect.top -= maxMove;
- taskRect.bottom -= maxMove;
- }
- taskResize(taskId, taskRect, delay_ms, false);
- }
- }
- // Return the remaining distance we didn't travel because we reached the target location.
- return maxToTravel;
- }
-
- private void runTaskSizeTaskTest() {
- final int taskId = Integer.parseInt(nextArgRequired());
- final int stepSize = Integer.parseInt(nextArgRequired());
- final String delayStr = nextArg();
- final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
- final StackInfo stackInfo;
- final Rect initialTaskBounds;
- try {
- stackInfo = mAm.getStackInfo(mAm.getFocusedStackId());
- initialTaskBounds = mAm.getTaskBounds(taskId);
- } catch (RemoteException e) {
- System.err.println("Error getting focus stack info or task bounds: " + e);
- return;
- }
- final Rect stackBounds = stackInfo.bounds;
- stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET);
- final Rect currentTaskBounds = new Rect(initialTaskBounds);
-
- // Size by top-left
- System.out.println("Growing top-left");
- do {
- currentTaskBounds.top -= getStepSize(
- currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.left -= getStepSize(
- currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.top < currentTaskBounds.top
- || stackBounds.left < currentTaskBounds.left);
-
- // Back to original size
- System.out.println("Shrinking top-left");
- do {
- currentTaskBounds.top += getStepSize(
- currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.left += getStepSize(
- currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.top > currentTaskBounds.top
- || initialTaskBounds.left > currentTaskBounds.left);
-
- // Size by top-right
- System.out.println("Growing top-right");
- do {
- currentTaskBounds.top -= getStepSize(
- currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.right += getStepSize(
- currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.top < currentTaskBounds.top
- || stackBounds.right > currentTaskBounds.right);
-
- // Back to original size
- System.out.println("Shrinking top-right");
- do {
- currentTaskBounds.top += getStepSize(
- currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
- stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.top > currentTaskBounds.top
- || initialTaskBounds.right < currentTaskBounds.right);
-
- // Size by bottom-left
- System.out.println("Growing bottom-left");
- do {
- currentTaskBounds.bottom += getStepSize(
- currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.left -= getStepSize(
- currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.bottom > currentTaskBounds.bottom
- || stackBounds.left < currentTaskBounds.left);
-
- // Back to original size
- System.out.println("Shrinking bottom-left");
- do {
- currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
- initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.left += getStepSize(
- currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.bottom < currentTaskBounds.bottom
- || initialTaskBounds.left > currentTaskBounds.left);
-
- // Size by bottom-right
- System.out.println("Growing bottom-right");
- do {
- currentTaskBounds.bottom += getStepSize(
- currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.right += getStepSize(
- currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.bottom > currentTaskBounds.bottom
- || stackBounds.right > currentTaskBounds.right);
-
- // Back to original size
- System.out.println("Shrinking bottom-right");
- do {
- currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
- initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
- stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.bottom < currentTaskBounds.bottom
- || initialTaskBounds.right < currentTaskBounds.right);
- }
-
- private int getStepSize(int current, int target, int inStepSize, boolean greaterThanTarget) {
- int stepSize = 0;
- if (greaterThanTarget && target < current) {
- current -= inStepSize;
- stepSize = inStepSize;
- if (target > current) {
- stepSize -= (target - current);
- }
- }
- if (!greaterThanTarget && target > current) {
- current += inStepSize;
- stepSize = inStepSize;
- if (target < current) {
- stepSize += (current - target);
- }
- }
- return stepSize;
- }
-
- private List<Configuration> getRecentConfigurations(int days) {
- IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
- Context.USAGE_STATS_SERVICE));
- final long now = System.currentTimeMillis();
- final long nDaysAgo = now - (days * 24 * 60 * 60 * 1000);
- try {
- @SuppressWarnings("unchecked")
- ParceledListSlice<ConfigurationStats> configStatsSlice = usm.queryConfigurationStats(
- UsageStatsManager.INTERVAL_BEST, nDaysAgo, now, "com.android.shell");
- if (configStatsSlice == null) {
- return Collections.emptyList();
- }
-
- final ArrayMap<Configuration, Integer> recentConfigs = new ArrayMap<>();
- final List<ConfigurationStats> configStatsList = configStatsSlice.getList();
- final int configStatsListSize = configStatsList.size();
- for (int i = 0; i < configStatsListSize; i++) {
- final ConfigurationStats stats = configStatsList.get(i);
- final int indexOfKey = recentConfigs.indexOfKey(stats.getConfiguration());
- if (indexOfKey < 0) {
- recentConfigs.put(stats.getConfiguration(), stats.getActivationCount());
- } else {
- recentConfigs.setValueAt(indexOfKey,
- recentConfigs.valueAt(indexOfKey) + stats.getActivationCount());
- }
- }
-
- final Comparator<Configuration> comparator = new Comparator<Configuration>() {
- @Override
- public int compare(Configuration a, Configuration b) {
- return recentConfigs.get(b).compareTo(recentConfigs.get(a));
- }
- };
-
- ArrayList<Configuration> configs = new ArrayList<>(recentConfigs.size());
- configs.addAll(recentConfigs.keySet());
- Collections.sort(configs, comparator);
- return configs;
-
- } catch (RemoteException e) {
- return Collections.emptyList();
- }
- }
-
- private void runGetConfig() throws Exception {
- int days = 14;
- String option = nextOption();
- if (option != null) {
- if (!option.equals("--days")) {
- throw new IllegalArgumentException("unrecognized option " + option);
- }
-
- days = Integer.parseInt(nextArgRequired());
- if (days <= 0) {
- throw new IllegalArgumentException("--days must be a positive integer");
- }
- }
-
- try {
- Configuration config = mAm.getConfiguration();
- if (config == null) {
- System.err.println("Activity manager has no configuration");
- return;
- }
-
- System.out.println("config: " + Configuration.resourceQualifierString(config));
- System.out.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS));
-
- final List<Configuration> recentConfigs = getRecentConfigurations(days);
- final int recentConfigSize = recentConfigs.size();
- if (recentConfigSize > 0) {
- System.out.println("recentConfigs:");
- }
-
- for (int i = 0; i < recentConfigSize; i++) {
- System.out.println(" config: " + Configuration.resourceQualifierString(
- recentConfigs.get(i)));
- }
-
- } catch (RemoteException e) {
- }
- }
-
- private void runSuppressResizeConfigChanges() throws Exception {
- boolean suppress = Boolean.valueOf(nextArgRequired());
-
- try {
- mAm.suppressResizeConfigChanges(suppress);
- } catch (RemoteException e) {
- System.err.println("Error suppressing resize config changes: " + e);
- }
- }
-
- private void runSetInactive() throws Exception {
- int userId = UserHandle.USER_CURRENT;
+ public void runInstrument() throws Exception {
+ Instrument instrument = new Instrument(mAm, mPm);
String opt;
while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- String packageName = nextArgRequired();
- String value = nextArgRequired();
-
- IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
- Context.USAGE_STATS_SERVICE));
- usm.setAppInactive(packageName, Boolean.parseBoolean(value), userId);
- }
-
- private void runGetInactive() throws Exception {
- int userId = UserHandle.USER_CURRENT;
-
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return;
- }
- }
- String packageName = nextArgRequired();
-
- IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
- Context.USAGE_STATS_SERVICE));
- boolean isIdle = usm.isAppInactive(packageName, userId);
- System.out.println("Idle=" + isIdle);
- }
-
- private void runSendTrimMemory() throws Exception {
- int userId = UserHandle.USER_CURRENT;
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
- if (userId == UserHandle.USER_ALL) {
- System.err.println("Error: Can't use user 'all'");
- return;
- }
+ if (opt.equals("-p")) {
+ instrument.profileFile = nextArgRequired();
+ } else if (opt.equals("-w")) {
+ instrument.wait = true;
+ } else if (opt.equals("-r")) {
+ instrument.rawMode = true;
+ } else if (opt.equals("-m")) {
+ instrument.proto = true;
+ } else if (opt.equals("-e")) {
+ 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")) {
+ instrument.noWindowAnimation = true;
+ } else if (opt.equals("--user")) {
+ instrument.userId = parseUserArg(nextArgRequired());
+ } else if (opt.equals("--abi")) {
+ instrument.abi = nextArgRequired();
} else {
System.err.println("Error: Unknown option: " + opt);
return;
}
}
- String proc = nextArgRequired();
- String levelArg = nextArgRequired();
- int level;
- switch (levelArg) {
- case "HIDDEN":
- level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
- break;
- case "RUNNING_MODERATE":
- level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
- break;
- case "BACKGROUND":
- level = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
- break;
- case "RUNNING_LOW":
- level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
- break;
- case "MODERATE":
- level = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
- break;
- case "RUNNING_CRITICAL":
- level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
- break;
- case "COMPLETE":
- level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
- break;
- default:
- System.err.println("Error: Unknown level option: " + levelArg);
- return;
+ if (instrument.userId == UserHandle.USER_ALL) {
+ System.err.println("Error: Can't start instrumentation with user 'all'");
+ return;
}
- if (!mAm.setProcessMemoryTrimLevel(proc, userId, level)) {
- System.err.println("Unknown error: failed to set trim level");
- }
- }
- private void runGetCurrentUser() throws Exception {
- UserInfo currentUser = Preconditions.checkNotNull(mAm.getCurrentUser(),
- "Current user not set");
- System.out.println(currentUser.id);
- }
+ instrument.componentNameArg = nextArgRequired();
- /**
- * Open the given file for sending into the system process. This verifies
- * with SELinux that the system will have access to the file.
- */
- private static ParcelFileDescriptor openForSystemServer(File file, int mode)
- throws FileNotFoundException {
- final ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, mode);
- final String tcon = SELinux.getFileContext(file.getAbsolutePath());
- if (!SELinux.checkSELinuxAccess("u:r:system_server:s0", tcon, "file", "read")) {
- throw new FileNotFoundException("System server has no access to file context " + tcon);
- }
- return fd;
- }
-
- private Rect getBounds() {
- String leftStr = nextArgRequired();
- int left = Integer.parseInt(leftStr);
- String topStr = nextArgRequired();
- int top = Integer.parseInt(topStr);
- String rightStr = nextArgRequired();
- int right = Integer.parseInt(rightStr);
- String bottomStr = nextArgRequired();
- int bottom = Integer.parseInt(bottomStr);
- if (left < 0) {
- System.err.println("Error: bad left arg: " + leftStr);
- return null;
- }
- if (top < 0) {
- System.err.println("Error: bad top arg: " + topStr);
- return null;
- }
- if (right <= 0) {
- System.err.println("Error: bad right arg: " + rightStr);
- return null;
- }
- if (bottom <= 0) {
- System.err.println("Error: bad bottom arg: " + bottomStr);
- return null;
- }
- return new Rect(left, top, right, bottom);
+ 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/app_process/Android.mk b/cmds/app_process/Android.mk
index e530184..e18de8e 100644
--- a/cmds/app_process/Android.mk
+++ b/cmds/app_process/Android.mk
@@ -62,7 +62,6 @@
LOCAL_LDFLAGS := -ldl
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_CPPFLAGS := -std=c++11
LOCAL_MODULE := app_process__asan
LOCAL_MULTILIB := both
@@ -70,7 +69,6 @@
LOCAL_MODULE_STEM_64 := app_process64
LOCAL_SANITIZE := address
-LOCAL_CLANG := true
LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)/asan
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp
index f6afc85..154cb25 100644
--- a/cmds/idmap/inspect.cpp
+++ b/cmds/idmap/inspect.cpp
@@ -2,6 +2,7 @@
#include <androidfw/AssetManager.h>
#include <androidfw/ResourceTypes.h>
+#include <utils/ByteOrder.h>
#include <utils/String8.h>
#include <fcntl.h>
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index 6d30f0d..ab6adfb 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -1,5 +1,6 @@
#include <dirent.h>
#include <inttypes.h>
+#include <sys/file.h>
#include <sys/stat.h>
#include "idmap.h"
@@ -35,16 +36,31 @@
bool writePackagesList(const char *filename, const SortedVector<Overlay>& overlayVector)
{
- FILE* fout = fopen(filename, "w");
+ // the file is opened for appending so that it doesn't get truncated
+ // before we can guarantee mutual exclusion via the flock
+ FILE* fout = fopen(filename, "a");
if (fout == NULL) {
return false;
}
+ if (TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_EX)) != 0) {
+ fclose(fout);
+ return false;
+ }
+
+ if (TEMP_FAILURE_RETRY(ftruncate(fileno(fout), 0)) != 0) {
+ TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_UN));
+ fclose(fout);
+ return false;
+ }
+
for (size_t i = 0; i < overlayVector.size(); ++i) {
const Overlay& overlay = overlayVector[i];
fprintf(fout, "%s %s\n", overlay.apk_path.string(), overlay.idmap_path.string());
}
+ TEMP_FAILURE_RETRY(fflush(fout));
+ TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_UN));
fclose(fout);
// Make file world readable since Zygote (running as root) will read
@@ -171,9 +187,6 @@
{
String8 filename = String8(idmap_dir);
filename.appendPath("overlays.list");
- if (unlink(filename.string()) != 0 && errno != ENOENT) {
- return EXIT_FAILURE;
- }
SortedVector<Overlay> overlayVector;
const size_t N = overlay_dirs->size();
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 32a8088..718f141 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -41,6 +41,10 @@
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.ApkLite;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
@@ -49,9 +53,12 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IUserManager;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -68,6 +75,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -76,6 +84,7 @@
public final class Pm {
private static final String TAG = "Pm";
+ private static final String STDIN_PATH = "-";
IPackageManager mPm;
IPackageInstaller mInstaller;
@@ -284,13 +293,45 @@
}
}
+ static final class MyShellCallback extends ShellCallback {
+ @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ File file = new File(path);
+ final ParcelFileDescriptor fd;
+ try {
+ fd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_WRITE_ONLY);
+ } catch (FileNotFoundException e) {
+ String msg = "Unable to open file " + path + ": " + e;
+ System.err.println(msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (seLinuxContext != null) {
+ final String tcon = SELinux.getFileContext(file.getAbsolutePath());
+ if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ String msg = "System server has no access to file context " + tcon;
+ System.err.println(msg + " (from path " + file.getAbsolutePath()
+ + ", context " + seLinuxContext + ")");
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ return fd;
+ }
+ }
+
private int runShellCommand(String serviceName, String[] args) {
final HandlerThread handlerThread = new HandlerThread("results");
handlerThread.start();
try {
ServiceManager.getService(serviceName).shellCommand(
FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- args, new ResultReceiver(new Handler(handlerThread.getLooper())));
+ args, new MyShellCallback(),
+ new ResultReceiver(new Handler(handlerThread.getLooper())));
return 0;
} catch (RemoteException e) {
e.printStackTrace();
@@ -362,12 +403,31 @@
*/
private int runInstall() throws RemoteException {
final InstallParams params = makeInstallParams();
+ final String inPath = nextArg();
+ if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
+ File file = new File(inPath);
+ if (file.isFile()) {
+ try {
+ ApkLite baseApk = PackageParser.parseApkLite(file, 0);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+ params.sessionParams.setSize(
+ PackageHelper.calculateInstalledSize(pkgLite, false,
+ params.sessionParams.abiOverride));
+ } catch (PackageParserException | IOException e) {
+ System.err.println("Error: Failed to parse APK file: " + e);
+ return 1;
+ }
+ } else {
+ System.err.println("Error: Can't open non-file: " + inPath);
+ return 1;
+ }
+ }
+
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
try {
- final String inPath = nextArg();
- if (inPath == null && params.sessionParams.sizeBytes == 0) {
+ if (inPath == null && params.sessionParams.sizeBytes == -1) {
System.err.println("Error: must either specify a package size or an APK file");
return 1;
}
@@ -484,7 +544,11 @@
}
break;
case "-S":
- sessionParams.setSize(Long.parseLong(nextOptionData()));
+ final long sizeBytes = Long.parseLong(nextOptionData());
+ if (sizeBytes <= 0) {
+ throw new IllegalArgumentException("Size must be positive");
+ }
+ sessionParams.setSize(sizeBytes);
break;
case "--abi":
sessionParams.abiOverride = checkAbiArgument(nextOptionData());
@@ -529,7 +593,7 @@
private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
- if ("-".equals(inPath)) {
+ if (STDIN_PATH.equals(inPath)) {
inPath = null;
} else if (inPath != null) {
final File file = new File(inPath);
@@ -945,7 +1009,7 @@
} else if (userId < 0) {
info = mUm.createUser(name, flags);
} else {
- info = mUm.createProfileForUser(name, flags, userId);
+ info = mUm.createProfileForUser(name, flags, userId, null);
}
if (info != null) {
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index a6ef25f..4dcb05e 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -50,7 +50,7 @@
IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
Context.USB_SERVICE));
try {
- usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null));
+ usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null), false);
} catch (RemoteException e) {
System.err.println("Error communicating with UsbManager: " + e);
}
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/compiled-classes-phone b/compiled-classes-phone
index 221d687..6214306 100644
--- a/compiled-classes-phone
+++ b/compiled-classes-phone
@@ -1168,14 +1168,6 @@
android.drm.DrmManagerClient$OnInfoListener
android.drm.DrmOutputStream
android.drm.DrmSupportInfo
-android.graphics.Atlas
-android.graphics.Atlas$Entry
-android.graphics.Atlas$Policy
-android.graphics.Atlas$SlicePolicy
-android.graphics.Atlas$SlicePolicy$Cell
-android.graphics.Atlas$SlicePolicy$MinAreaSplitDecision
-android.graphics.Atlas$SlicePolicy$SplitDecision
-android.graphics.Atlas$Type
android.graphics.Bitmap
android.graphics.Bitmap$1
android.graphics.Bitmap$CompressFormat
@@ -4264,9 +4256,6 @@
android.view.IAppTransitionAnimationSpecsFuture$Stub$Proxy
android.view.IApplicationToken
android.view.IApplicationToken$Stub
-android.view.IAssetAtlas
-android.view.IAssetAtlas$Stub
-android.view.IAssetAtlas$Stub$Proxy
android.view.IDockedStackListener
android.view.IDockedStackListener$Stub
android.view.IDockedStackListener$Stub$Proxy
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index fd0bf0b..116d063 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -16,15 +16,15 @@
package android.animation;
-import java.util.Arrays;
-import java.util.List;
-
-import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.FloatKeyframe;
+import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.ObjectKeyframe;
import android.graphics.Path;
import android.util.Log;
+import java.util.Arrays;
+import java.util.List;
+
/**
* This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
* values between those keyframes for a given animation. The class internal to the animation
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index a09c920..ed7e89d 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -276,6 +276,23 @@
}
/**
+ * Returns whether animators are currently enabled, system-wide. By default, all
+ * animators are enabled. This can change if either the user sets a Developer Option
+ * to set the animator duration scale to 0 or by Battery Savery mode being enabled
+ * (which disables all animations).
+ *
+ * <p>Developers should not typically need to call this method, but should an app wish
+ * to show a different experience when animators are disabled, this return value
+ * can be used as a decider of which experience to offer.
+ *
+ * @return boolean Whether animators are currently enabled. The default value is
+ * <code>true</code>.
+ */
+ public static boolean areAnimatorsEnabled() {
+ return !(sDurationScale == 0);
+ }
+
+ /**
* Creates a new ValueAnimator object. This default constructor is primarily for
* use internally; the factory methods which take parameters are more generally
* useful.
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 f798512..a2f7aea 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);
@@ -1229,8 +1229,19 @@
case UPDATE_CONFIGURATION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Configuration config = Configuration.CREATOR.createFromParcel(data);
- updateConfiguration(config);
+ final boolean updated = updateConfiguration(config);
reply.writeNoException();
+ reply.writeInt(updated ? 1 : 0);
+ 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;
}
@@ -1420,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();
@@ -1433,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();
@@ -1496,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();
@@ -2060,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();
@@ -2546,15 +2557,6 @@
return true;
}
- case DELETE_ACTIVITY_CONTAINER_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
- IActivityContainer activityContainer =
- IActivityContainer.Stub.asInterface(data.readStrongBinder());
- deleteActivityContainer(activityContainer);
- reply.writeNoException();
- return true;
- }
-
case CREATE_STACK_ON_DISPLAY: {
data.enforceInterface(IActivityManager.descriptor);
int displayId = data.readInt();
@@ -2930,6 +2932,22 @@
reply.writeNoException();
return true;
}
+ case GET_DEFAULT_PICTURE_IN_PICTURE_BOUNDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int displayId = data.readInt();
+ Rect r = getDefaultPictureInPictureBounds(displayId);
+ reply.writeNoException();
+ r.writeToParcel(reply, 0);
+ return true;
+ }
+ case GET_PICTURE_IN_PICTURE_MOVEMENT_BOUNDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int displayId = data.readInt();
+ Rect r = getPictureInPictureMovementBounds(displayId);
+ reply.writeNoException();
+ r.writeToParcel(reply, 0);
+ return true;
+ }
case SET_VR_MODE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
@@ -4593,16 +4611,31 @@
data.recycle();
return res;
}
- public void updateConfiguration(Configuration values) throws RemoteException
- {
+ public boolean updateConfiguration(Configuration values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
values.writeToParcel(data, 0);
mRemote.transact(UPDATE_CONFIGURATION_TRANSACTION, data, reply, 0);
reply.readException();
+ boolean updated = reply.readInt() == 1;
data.recycle();
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 {
@@ -6356,18 +6389,6 @@
return res;
}
- public void deleteActivityContainer(IActivityContainer activityContainer)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeStrongBinder(activityContainer.asBinder());
- mRemote.transact(DELETE_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0);
- reply.readException();
- data.recycle();
- reply.recycle();
- }
-
public boolean startBinderTracking() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -6986,6 +7007,36 @@
}
@Override
+ public Rect getDefaultPictureInPictureBounds(int displayId) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(displayId);
+ 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(int displayId) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(displayId);
+ 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/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 6458d6f..ec21882 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -206,6 +206,7 @@
private ArrayList<Matrix> mSharedElementParentMatrices;
private boolean mSharedElementTransitionComplete;
private boolean mViewsTransitionComplete;
+ private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>();
public ActivityTransitionCoordinator(Window window,
ArrayList<String> allSharedElementNames,
@@ -287,7 +288,7 @@
View view = mTransitioningViews.get(i);
if (!view.getGlobalVisibleRect(r)) {
mTransitioningViews.remove(i);
- showView(view, true);
+ mStrippedTransitioningViews.add(view);
}
}
}
@@ -360,6 +361,12 @@
}
}
}
+ if (mStrippedTransitioningViews != null) {
+ for (int i = mStrippedTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mStrippedTransitioningViews.get(i);
+ set.excludeTarget(view, true);
+ }
+ }
// By adding the transition after addTarget, we prevent addTarget from
// affecting transition.
set.addTransition(transition);
@@ -679,6 +686,7 @@
mWindow = null;
mSharedElements.clear();
mTransitioningViews = null;
+ mStrippedTransitioningViews = null;
mOriginalAlphas.clear();
mResultReceiver = null;
mPendingTransition = null;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5a9498f..191cc49 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -311,9 +311,7 @@
/** Access APIs for SIP calling over VOIP or WiFi */
public static final String OPSTR_USE_SIP
= "android:use_sip";
- /** Access APIs for diverting outgoing calls
- * @hide
- */
+ /** Access APIs for diverting outgoing calls */
public static final String OPSTR_PROCESS_OUTGOING_CALLS
= "android:process_outgoing_calls";
/** Use the fingerprint API. */
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/Dialog.java b/core/java/android/app/Dialog.java
index 85a0403..6e2c464 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
+import android.content.res.Configuration;
import android.content.pm.ApplicationInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -288,9 +289,14 @@
}
mCanceled = false;
-
+
if (!mCreated) {
dispatchOnCreate(null);
+ } else {
+ // Fill the DecorView in on any configuration changes that
+ // may have occured while it was removed from the WindowManager.
+ final Configuration config = mContext.getResources().getConfiguration();
+ mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c6736eef..27a0200 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -123,6 +123,7 @@
mIsReadyForTransition = true;
hideViews(mSharedElements);
if (getViewsTransition() != null && mTransitioningViews != null) {
+ stripOffscreenViews();
hideViews(mTransitioningViews);
}
if (mIsReturning) {
@@ -518,9 +519,6 @@
mIsViewsTransitionStarted = true;
if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
viewsTransition = configureTransition(getViewsTransition(), true);
- if (viewsTransition != null && !mIsReturning) {
- stripOffscreenViews();
- }
}
if (viewsTransition == null) {
viewsTransitionComplete();
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 096f0cb..3670c58 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -274,10 +274,23 @@
/**
* 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 void updateConfiguration(Configuration values) throws RemoteException;
+ 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;
@@ -567,8 +580,6 @@
public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException;
- public void deleteActivityContainer(IActivityContainer container) throws RemoteException;
-
public int getActivityDisplayId(IBinder activityToken) throws RemoteException;
public void startSystemLockTaskMode(int taskId) throws RemoteException;
@@ -646,6 +657,16 @@
public void enterPictureInPictureMode(IBinder token) throws RemoteException;
+ /**
+ * @return the default bounds of the PIP on the default display.
+ */
+ public Rect getDefaultPictureInPictureBounds(int displayId) throws RemoteException;
+
+ /**
+ * @return the movement bounds of the PIP on the default display.
+ */
+ public Rect getPictureInPictureMovementBounds(int displayId) throws RemoteException;
+
public int setVrMode(IBinder token, boolean enabled, ComponentName packageName)
throws RemoteException;
@@ -1090,4 +1111,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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 28224e8..c87eef9 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -78,6 +78,8 @@
void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
void cancelNotificationsFromListener(in INotificationListener token, in String[] keys);
+ void snoozeNotificationFromListener(in INotificationListener token, String key, long until);
+
void requestBindListener(in ComponentName component);
void requestUnbindListener(in INotificationListener token);
void requestBindProvider(in ComponentName component);
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index f33af39..e4a22c4 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -46,7 +46,8 @@
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For a detailed discussion about how to create services, read the
- * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p>
+ * <a href="{@docRoot}guide/components/services.html">Services</a> developer
+ * guide.</p>
* </div>
*
* @see android.os.AsyncTask
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 680e518..bdb2685 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -19,6 +19,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -1052,6 +1053,7 @@
this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false);
}
+ /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
this.mIcon = icon;
@@ -1118,7 +1120,7 @@
*/
@Deprecated
public Builder(int icon, CharSequence title, PendingIntent intent) {
- this(Icon.createWithResource("", icon), title, intent, new Bundle(), null);
+ this(Icon.createWithResource("", icon), title, intent);
}
/**
@@ -1128,7 +1130,7 @@
* @param intent the {@link PendingIntent} to fire when users trigger this action
*/
public Builder(Icon icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null);
+ this(icon, title, intent, new Bundle(), null, false);
}
/**
@@ -1137,12 +1139,13 @@
* @param action the action to read fields from.
*/
public Builder(Action action) {
- this(action.getIcon(), action.title, action.actionIntent, new Bundle(action.mExtras),
- action.getRemoteInputs());
+ this(action.getIcon(), action.title, action.actionIntent,
+ new Bundle(action.mExtras), action.getRemoteInputs(),
+ action.getAllowGeneratedReplies());
}
private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
- RemoteInput[] remoteInputs) {
+ RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
mIcon = icon;
mTitle = title;
mIntent = intent;
@@ -1151,6 +1154,7 @@
mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
Collections.addAll(mRemoteInputs, remoteInputs);
}
+ mAllowGeneratedReplies = allowGeneratedReplies;
}
/**
@@ -4683,12 +4687,12 @@
}
/**
- * @param userDisplayName the name to be displayed for any replies sent by the user before the
- * posting app reposts the notification with those messages after they've been actually
- * sent and in previous messages sent by the user added in
+ * @param userDisplayName Required - the name to be displayed for any replies sent by the
+ * user before the posting app reposts the notification with those messages after they've
+ * been actually sent and in previous messages sent by the user added in
* {@link #addMessage(Notification.MessagingStyle.Message)}
*/
- public MessagingStyle(CharSequence userDisplayName) {
+ public MessagingStyle(@NonNull CharSequence userDisplayName) {
mUserDisplayName = userDisplayName;
}
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/tests/coretests/src/android/print/mockservice/SettingsActivity.java b/core/java/android/app/ProfilerInfo.aidl
similarity index 69%
copy from core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
copy to core/java/android/app/ProfilerInfo.aidl
index fb76e67..dc744b9 100644
--- a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
+++ b/core/java/android/app/ProfilerInfo.aidl
@@ -14,15 +14,7 @@
* limitations under the License.
*/
-package android.print.mockservice;
+package android.app;
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
+/** @hide */
+parcelable ProfilerInfo;
\ No newline at end of file
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index c88cea0..2d15beb 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -839,19 +839,22 @@
tmpConfig = new Configuration();
}
tmpConfig.setTo(config);
+
+ // Get new DisplayMetrics based on the DisplayAdjustments given
+ // to the ResourcesImpl. Update a copy if the CompatibilityInfo
+ // changed, because the ResourcesImpl object will handle the
+ // update internally.
+ DisplayAdjustments daj = r.getDisplayAdjustments();
+ if (compat != null) {
+ daj = new DisplayAdjustments(daj);
+ daj.setCompatibilityInfo(compat);
+ }
+ dm = getDisplayMetrics(displayId, daj);
+
if (!isDefaultDisplay) {
- // Get new DisplayMetrics based on the DisplayAdjustments given
- // to the ResourcesImpl. Udate a copy if the CompatibilityInfo
- // changed, because the ResourcesImpl object will handle the
- // update internally.
- DisplayAdjustments daj = r.getDisplayAdjustments();
- if (compat != null) {
- daj = new DisplayAdjustments(daj);
- daj.setCompatibilityInfo(compat);
- }
- dm = getDisplayMetrics(displayId, daj);
applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
}
+
if (hasOverrideConfiguration) {
tmpConfig.updateFrom(key.mOverrideConfiguration);
}
diff --git a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java b/core/java/android/app/ResultInfo.aidl
similarity index 69%
copy from core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
copy to core/java/android/app/ResultInfo.aidl
index fb76e67..6c12e1c 100644
--- a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
+++ b/core/java/android/app/ResultInfo.aidl
@@ -14,15 +14,7 @@
* limitations under the License.
*/
-package android.print.mockservice;
+package android.app;
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
+/** @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 a207a52..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;
@@ -2352,18 +2353,23 @@
* <p>The calling device admin must be a device or profile owner. If it is not,
* a {@link SecurityException} will be thrown.
*
+ * <p>The calling device admin can verify the value it has set by calling
+ * {@link #getRequiredStrongAuthTimeout(ComponentName)} and passing in its instance.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance returned by
* {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
* profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param timeoutMs The new timeout, after which the user will have to unlock with strong
- * authentication method. If the timeout is lower than 1 hour (minimum) or higher than
- * 72 hours (default and maximum) an {@link IllegalArgumentException} is thrown.
+ * authentication method. A value of 0 means the admin is not participating in
+ * controlling the timeout.
+ * The minimum and maximum timeouts are platform-defined and are typically 1 hour and
+ * 72 hours, respectively. Though discouraged, the admin may choose to require strong
+ * auth at all times using {@link #KEYGUARD_DISABLE_FINGERPRINT} and/or
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS}.
*
* @throws SecurityException if {@code admin} is not a device or profile owner.
- * @throws IllegalArgumentException if the timeout is lower than 1 hour (minimum) or higher than
- * 72 hours (default and maximum)
*
* @hide
*/
@@ -2389,7 +2395,7 @@
*
* @param admin The name of the admin component to check, or {@code null} to aggregate
* accross all participating admins.
- * @return The timeout or default timeout if not configured
+ * @return The timeout or 0 if not configured for the provided admin.
*
* @hide
*/
@@ -3518,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/res/res/values-mcc238-mnc06/config.xml b/core/java/android/app/admin/PasswordMetrics.aidl
similarity index 64%
rename from core/res/res/values-mcc238-mnc06/config.xml
rename to core/java/android/app/admin/PasswordMetrics.aidl
index afc0cc4..90d7c69 100644
--- a/core/res/res/values-mcc238-mnc06/config.xml
+++ b/core/java/android/app/admin/PasswordMetrics.aidl
@@ -1,7 +1,6 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
/*
-** Copyright 2014, The Android Open Source Project
+**
+** 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.
@@ -15,9 +14,7 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- SIM does not save, but the voice mail number to be changed. -->
- <bool name="editable_voicemailnumber">true</bool>
-</resources>
+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/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index bc82806..bad6325 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -35,6 +35,8 @@
import android.util.ArraySet;
import android.util.Log;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
@@ -647,10 +649,11 @@
File file = scanQueue.remove(0);
String filePath;
try {
- // Ignore symlinks outright
+ // Ignore things that aren't "real" files or dirs
StructStat stat = Os.lstat(file.getPath());
- if (OsConstants.S_ISLNK(stat.st_mode)) {
- if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file);
+ if (!OsConstants.S_ISREG(stat.st_mode)
+ && !OsConstants.S_ISDIR(stat.st_mode)) {
+ if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file);
continue;
}
@@ -921,6 +924,13 @@
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
+
+ // Don't close the fd out from under the system service if this was local
+ if (Binder.getCallingPid() != Process.myPid()) {
+ IoUtils.closeQuietly(oldState);
+ IoUtils.closeQuietly(data);
+ IoUtils.closeQuietly(newState);
+ }
}
}
@@ -951,6 +961,11 @@
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
+
+ if (Binder.getCallingPid() != Process.myPid()) {
+ IoUtils.closeQuietly(data);
+ IoUtils.closeQuietly(newState);
+ }
}
}
@@ -994,6 +1009,10 @@
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
+
+ if (Binder.getCallingPid() != Process.myPid()) {
+ IoUtils.closeQuietly(data);
+ }
}
}
@@ -1041,6 +1060,10 @@
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
+
+ if (Binder.getCallingPid() != Process.myPid()) {
+ IoUtils.closeQuietly(data);
+ }
}
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index c71dc7c..542b06b 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -789,19 +789,13 @@
public boolean enableBLE() {
if (!isBleScanAlwaysAvailable()) return false;
- if (isLeEnabled() == true) {
- if (DBG) Log.d(TAG, "enableBLE(): BT is already enabled..!");
- try {
- mManagerService.updateBleAppCount(mToken, true);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- }
- return true;
- }
-
try {
- if (DBG) Log.d(TAG, "Calling enableBLE");
mManagerService.updateBleAppCount(mToken, true);
+ if (isLeEnabled()) {
+ if (DBG) Log.d(TAG, "enableBLE(): Bluetooth already enabled");
+ return true;
+ }
+ if (DBG) Log.d(TAG, "enableBLE(): Calling enable");
return mManagerService.enable(ActivityThread.currentPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
@@ -1522,6 +1516,36 @@
}
/**
+ * Gets the currently supported profiles by the adapter.
+ *
+ *<p> This can be used to check whether a profile is supported before attempting
+ * to connect to its respective proxy.
+ *
+ * @return a list of integers indicating the ids of supported profiles as defined in
+ * {@link BluetoothProfile}.
+ * @hide
+ */
+ public List<Integer> getSupportedProfiles() {
+ final ArrayList<Integer> supportedProfiles = new ArrayList<Integer>();
+
+ try {
+ synchronized (mManagerCallback) {
+ if (mService != null) {
+ final long supportedProfilesBitMask = mService.getSupportedProfiles();
+
+ for (int i = 0; i <= BluetoothProfile.MAX_PROFILE_ID; i++) {
+ if ((supportedProfilesBitMask & (1 << i)) != 0) {
+ supportedProfiles.add(i);
+ }
+ }
+ }
+ }
+ } catch (RemoteException e) {Log.e(TAG, "getSupportedProfiles:", e);}
+
+ return supportedProfiles;
+ }
+
+ /**
* Get the current connection state of the local Bluetooth adapter.
* This can be used to check whether the local Bluetooth adapter is connected
* to any profile of any other remote Bluetooth Device.
@@ -2030,7 +2054,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;
@@ -2052,7 +2076,7 @@
}
public void onBluetoothServiceDown() {
- if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
+ if (DBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
try {
mServiceLock.writeLock().lock();
@@ -2080,7 +2104,7 @@
}
public void onBrEdrDown() {
- if (VDBG) Log.i(TAG, "on QBrEdrDown: ");
+ if (DBG) Log.i(TAG, "onBrEdrDown:");
}
};
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index cd5eff2..5c9e2ee 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -895,6 +895,14 @@
return false;
}
+ /** @hide */
+ public boolean isBondingInitiatedLocally() {
+ try {
+ return sService.isBondingInitiatedLocally(this);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
/**
* Set the Out Of Band data for a remote device to be used later
* in the pairing mechanism. Users can obtain this data through other
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/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index eee66d1..20d95cc 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -137,6 +137,13 @@
public static final int PBAP_CLIENT = 17;
/**
+ * Max profile ID. This value should be updated whenever a new profile is added to match
+ * the largest value assigned to a profile.
+ * @hide
+ */
+ public static final int MAX_PROFILE_ID = 17;
+
+ /**
* Default priority for devices that we try to auto-connect to and
* and allow incoming connections for the profile
* @hide
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/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index a420539..96a1ae8 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -62,6 +62,8 @@
boolean cancelBondProcess(in BluetoothDevice device);
boolean removeBond(in BluetoothDevice device);
int getBondState(in BluetoothDevice device);
+ boolean isBondingInitiatedLocally(in BluetoothDevice device);
+ long getSupportedProfiles();
int getConnectionState(in BluetoothDevice device);
String getRemoteName(in BluetoothDevice device);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 74f48c6..e031275 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3037,6 +3037,18 @@
"android.intent.action.MANAGED_PROFILE_UNAVAILABLE";
/**
+ * Broadcast sent to the system user when the 'device locked' state changes for any user.
+ * Carries an extra {@link #EXTRA_USER_HANDLE} that specifies the ID of the user for which
+ * the device was locked or unlocked.
+ *
+ * This is only sent to registered receivers.
+ *
+ * @hide
+ */
+ public static final String ACTION_DEVICE_LOCKED_CHANGED =
+ "android.intent.action.DEVICE_LOCKED_CHANGED";
+
+ /**
* Sent when the user taps on the clock widget in the system's "quick settings" area.
*/
public static final String ACTION_QUICK_CLOCK =
@@ -4184,6 +4196,16 @@
"android.intent.extra.MEDIA_RESOURCE_TYPE";
/**
+ * Used as a boolean extra field in {@link #ACTION_CHOOSER} intents to specify
+ * whether to show the chooser or not when there is only one application available
+ * to choose from.
+ *
+ * @hide
+ */
+ public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE =
+ "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
+
+ /**
* Used as an int value for {@link #EXTRA_MEDIA_RESOURCE_TYPE}
* to represent that a video codec is allowed to use.
*
@@ -6574,7 +6596,7 @@
*/
public void removeUnsafeExtras() {
if (mExtras != null) {
- mExtras.filterValues();
+ mExtras = mExtras.filterValues();
}
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 5d2c6cd..4cbff5f 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -163,13 +163,14 @@
*/
public static final int RESIZE_MODE_UNRESIZEABLE = 0;
/**
- * Activity can not be resized and always occupies the fullscreen area with all windows cropped
- * to either the task or stack bounds.
+ * Activity didn't explicitly request to be resizeable, but we are making it resizeable because
+ * of the SDK version it targets. Only affects apps with target SDK >= N where the app is
+ * implied to be resizeable if it doesn't explicitly set the attribute to any value.
* @hide
*/
- public static final int RESIZE_MODE_CROP_WINDOWS = 1;
+ public static final int RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION = 1;
/**
- * Activity is resizeable.
+ * Activity explicitly requested to be resizeable.
* @hide
*/
public static final int RESIZE_MODE_RESIZEABLE = 2;
@@ -179,7 +180,8 @@
*/
public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3;
/**
- * Activity is does not support resizing, but we are forcing it to be resizeable.
+ * Activity is does not support resizing, but we are forcing it to be resizeable. Only affects
+ * certain pre-N apps where we force them to be resizeable.
* @hide
*/
public static final int RESIZE_MODE_FORCE_RESIZEABLE = 4;
@@ -862,7 +864,8 @@
public static boolean isResizeableMode(int mode) {
return mode == RESIZE_MODE_RESIZEABLE
|| mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE
- || mode == RESIZE_MODE_FORCE_RESIZEABLE;
+ || mode == RESIZE_MODE_FORCE_RESIZEABLE
+ || mode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
}
/** @hide */
@@ -870,8 +873,8 @@
switch (mode) {
case RESIZE_MODE_UNRESIZEABLE:
return "RESIZE_MODE_UNRESIZEABLE";
- case RESIZE_MODE_CROP_WINDOWS:
- return "RESIZE_MODE_CROP_WINDOWS";
+ case RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
+ return "RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION";
case RESIZE_MODE_RESIZEABLE:
return "RESIZE_MODE_RESIZEABLE";
case RESIZE_MODE_RESIZEABLE_AND_PIPABLE:
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b7364e2..ecfc00f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -520,12 +520,26 @@
public static final int PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER = 1 << 10;
/**
- * When set, the activities associated with this application are resizeable by default.
+ * When set, the application explicitly requested that its activities by resizeable by default.
* @see android.R.styleable#AndroidManifestActivity_resizeableActivity
*
* @hide
*/
- public static final int PRIVATE_FLAG_RESIZEABLE_ACTIVITIES = 1 << 11;
+ public static final int PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET = 1 << 11;
+
+ /**
+ * The application isn't requesting explicitly requesting for its activities to be resizeable or
+ * non-resizeable by default. So, we are making it activities resizeable by default based on the
+ * target SDK version of the app.
+ * @see android.R.styleable#AndroidManifestActivity_resizeableActivity
+ *
+ * NOTE: This only affects apps with target SDK >= N where the resizeableActivity attribute was
+ * introduced. It shouldn't be confused with {@link ActivityInfo#RESIZE_MODE_FORCE_RESIZEABLE}
+ * where certain pre-N apps are forced to the resizeable.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION = 1 << 12;
/**
* Value for {@link #privateFlags}: {@code true} means the OS should go ahead and
@@ -533,7 +547,7 @@
* foreground-equivalent run state. Defaults to {@code false} if unspecified.
* @hide
*/
- public static final int PRIVATE_FLAG_BACKUP_IN_FOREGROUND = 1 << 12;
+ public static final int PRIVATE_FLAG_BACKUP_IN_FOREGROUND = 1 << 13;
/**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index f5bcf64..da4eb2d 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -168,4 +168,25 @@
* @return Whether was launched.
*/
public abstract boolean wasPackageEverLaunched(String packageName, int userId);
+
+ /**
+ * Grants a runtime permission
+ * @param packageName The package name.
+ * @param name The name of the permission.
+ * @param userId The userId for which to grant the permission.
+ * @param overridePolicy If true, grant this permission even if it is fixed by policy.
+ */
+ public abstract void grantRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy);
+
+ /**
+ * Revokes a runtime permission
+ * @param packageName The package name.
+ * @param name The name of the permission.
+ * @param userId The userId for which to revoke the permission.
+ * @param overridePolicy If true, revoke this permission even if it is fixed by policy.
+ */
+ public abstract void revokeRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy);
+
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 201ada1..12e8d487 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -83,10 +83,12 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
@@ -2940,9 +2942,12 @@
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
}
- if (sa.getBoolean(R.styleable.AndroidManifestApplication_resizeableActivity,
- owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N)) {
- ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES;
+ if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestApplication_resizeableActivity, true)) {
+ ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET;
+ }
+ } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
+ ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
}
ai.networkSecurityConfigRes = sa.getResourceId(
@@ -3548,31 +3553,7 @@
R.styleable.AndroidManifestActivity_screenOrientation,
SCREEN_ORIENTATION_UNSPECIFIED);
- a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- final boolean appDefault = (owner.applicationInfo.privateFlags
- & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES) != 0;
- // This flag is used to workaround the issue with ignored resizeableActivity param when
- // either targetSdkVersion is not set at all or <uses-sdk> tag is below <application>
- // tag in AndroidManifest. If this param was explicitly set to 'false' we need to set
- // corresponding resizeMode regardless of targetSdkVersion value at this point in time.
- final boolean resizeableSetExplicitly
- = sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity);
- final boolean resizeable = sa.getBoolean(
- R.styleable.AndroidManifestActivity_resizeableActivity, appDefault);
-
- if (resizeable) {
- if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture,
- false)) {
- a.info.resizeMode = RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
- } else {
- a.info.resizeMode = RESIZE_MODE_RESIZEABLE;
- }
- } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N
- || resizeableSetExplicitly) {
- a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- } else if (!a.info.isFixedOrientation() && (a.info.flags & FLAG_IMMERSIVE) == 0) {
- a.info.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
- }
+ setActivityResizeMode(a.info, sa, owner);
if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) {
a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
@@ -3706,6 +3687,39 @@
return a;
}
+ private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) {
+ final boolean appExplicitDefault = (owner.applicationInfo.privateFlags
+ & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET) != 0;
+ final boolean supportsPip =
+ sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, false);
+
+ if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
+ || appExplicitDefault) {
+ // Activity or app explicitly set if it is resizeable or not;
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
+ appExplicitDefault)) {
+ aInfo.resizeMode =
+ supportsPip ? RESIZE_MODE_RESIZEABLE_AND_PIPABLE : RESIZE_MODE_RESIZEABLE;
+ } else {
+ aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ }
+ return;
+ }
+
+ if ((owner.applicationInfo.privateFlags
+ & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION) != 0) {
+ // The activity or app didn't explicitly set the resizing option, however we want to
+ // make it resize due to the sdk version it is targeting.
+ aInfo.resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+ return;
+ }
+
+ // resize preference isn't set and target sdk version doesn't support resizing apps by
+ // default. For the app to be resizeable if it isn't fixed orientation or immersive.
+ aInfo.resizeMode = (aInfo.isFixedOrientation() || (aInfo.flags & FLAG_IMMERSIVE) != 0)
+ ? RESIZE_MODE_UNRESIZEABLE : RESIZE_MODE_FORCE_RESIZEABLE;
+ }
+
private void parseLayout(Resources res, AttributeSet attrs, Activity a) {
TypedArray sw = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestLayout);
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 cd248ea..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 they are 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/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 4ad86f7..b0d0d79 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,6 +27,8 @@
import android.util.SparseArray;
import android.util.TypedValue;
+import dalvik.annotation.optimization.FastNative;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -752,6 +754,7 @@
* applications.
* {@hide}
*/
+ @FastNative
public native final void setConfiguration(int mcc, int mnc, String locale,
int orientation, int touchscreen, int density, int keyboard,
int keyboardHidden, int navigation, int screenWidth, int screenHeight,
@@ -761,13 +764,18 @@
/**
* Retrieve the resource identifier for the given resource name.
*/
+ @FastNative
/*package*/ native final int getResourceIdentifier(String type,
String name,
String defPackage);
+ @FastNative
/*package*/ native final String getResourceName(int resid);
+ @FastNative
/*package*/ native final String getResourcePackageName(int resid);
+ @FastNative
/*package*/ native final String getResourceTypeName(int resid);
+ @FastNative
/*package*/ native final String getResourceEntryName(int resid);
private native final long openAsset(String fileName, int accessMode);
@@ -781,15 +789,19 @@
private native final int readAssetChar(long asset);
private native final int readAsset(long asset, byte[] b, int off, int len);
private native final long seekAsset(long asset, long offset, int whence);
+ @FastNative
private native final long getAssetLength(long asset);
+ @FastNative
private native final long getAssetRemainingLength(long asset);
/** Returns true if the resource was found, filling in mRetStringBlock and
* mRetData. */
+ @FastNative
private native final int loadResourceValue(int ident, short density, TypedValue outValue,
boolean resolve);
/** Returns true if the resource was found, filling in mRetStringBlock and
* mRetData. */
+ @FastNative
private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
boolean resolve);
/*package*/ static final int STYLE_NUM_ENTRIES = 6;
@@ -802,17 +814,24 @@
static final int STYLE_CHANGING_CONFIGURATIONS = 4;
/*package*/ static final int STYLE_DENSITY = 5;
+ @FastNative
/*package*/ native static final boolean applyStyle(long theme,
int defStyleAttr, int defStyleRes, long xmlParser,
int[] inAttrs, int[] outValues, int[] outIndices);
+ @FastNative
/*package*/ native static final boolean resolveAttrs(long theme,
int defStyleAttr, int defStyleRes, int[] inValues,
int[] inAttrs, int[] outValues, int[] outIndices);
+ @FastNative
/*package*/ native final boolean retrieveAttributes(
long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
+ @FastNative
/*package*/ native final int getArraySize(int resource);
+ @FastNative
/*package*/ native final int retrieveArray(int resource, int[] outValues);
+ @FastNative
private native final int getStringBlockCount();
+ @FastNative
private native final long getNativeStringBlock(int block);
/**
@@ -845,17 +864,22 @@
/*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
/*package*/ native static final void copyTheme(long dest, long source);
/*package*/ native static final void clearTheme(long theme);
+ @FastNative
/*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
TypedValue outValue,
boolean resolve);
/*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);
+ @FastNative
/*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme);
private native final long openXmlAssetNative(int cookie, String fileName);
private native final String[] getArrayStringResource(int arrayRes);
+ @FastNative
private native final int[] getArrayStringInfo(int arrayRes);
+ @FastNative
/*package*/ native final int[] getArrayIntResource(int arrayRes);
+ @FastNative
/*package*/ native final int[] getStyleAttributes(int themeRes);
private native final void init(boolean isSystem);
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 64b6bf1..b03ed1e 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -59,7 +59,8 @@
mOverlayDirs = overlayDirs;
mLibDirs = libDirs;
mDisplayId = displayId;
- mOverrideConfiguration = overrideConfig != null ? overrideConfig : Configuration.EMPTY;
+ mOverrideConfiguration = new Configuration(overrideConfig != null
+ ? overrideConfig : Configuration.EMPTY);
mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
int hash = 17;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 2f4d69b0..e6b95741 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -20,6 +20,8 @@
import com.android.internal.util.XmlUtils;
+import dalvik.annotation.optimization.FastNative;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
@@ -492,25 +494,42 @@
int offset,
int size);
private static final native long nativeGetStringBlock(long obj);
-
private static final native long nativeCreateParseState(long obj);
- /*package*/ static final native int nativeNext(long state);
- private static final native int nativeGetNamespace(long state);
- /*package*/ static final native int nativeGetName(long state);
- private static final native int nativeGetText(long state);
- private static final native int nativeGetLineNumber(long state);
- private static final native int nativeGetAttributeCount(long state);
- private static final native int nativeGetAttributeNamespace(long state, int idx);
- private static final native int nativeGetAttributeName(long state, int idx);
- private static final native int nativeGetAttributeResource(long state, int idx);
- private static final native int nativeGetAttributeDataType(long state, int idx);
- private static final native int nativeGetAttributeData(long state, int idx);
- private static final native int nativeGetAttributeStringValue(long state, int idx);
- private static final native int nativeGetIdAttribute(long state);
- private static final native int nativeGetClassAttribute(long state);
- private static final native int nativeGetStyleAttribute(long state);
- private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
private static final native void nativeDestroyParseState(long state);
-
private static final native void nativeDestroy(long obj);
+
+ // ----------- @FastNative ------------------
+
+ @FastNative
+ /*package*/ static final native int nativeNext(long state);
+ @FastNative
+ private static final native int nativeGetNamespace(long state);
+ @FastNative
+ /*package*/ static final native int nativeGetName(long state);
+ @FastNative
+ private static final native int nativeGetText(long state);
+ @FastNative
+ private static final native int nativeGetLineNumber(long state);
+ @FastNative
+ private static final native int nativeGetAttributeCount(long state);
+ @FastNative
+ private static final native int nativeGetAttributeNamespace(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeName(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeResource(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeDataType(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeData(long state, int idx);
+ @FastNative
+ private static final native int nativeGetAttributeStringValue(long state, int idx);
+ @FastNative
+ private static final native int nativeGetIdAttribute(long state);
+ @FastNative
+ private static final native int nativeGetClassAttribute(long state);
+ @FastNative
+ private static final native int nativeGetStyleAttribute(long state);
+ @FastNative
+ private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 2dd4800..bb8d9ff 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -20,6 +20,7 @@
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;
+import java.io.File;
/**
* A helper class to manage database creation and version management.
@@ -54,6 +55,7 @@
private final String mName;
private final CursorFactory mFactory;
private final int mNewVersion;
+ private final int mMinimumSupportedVersion;
private SQLiteDatabase mDatabase;
private boolean mIsInitializing;
@@ -96,6 +98,34 @@
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
+ this(context, name, factory, version, 0, errorHandler);
+ }
+
+ /**
+ * Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)}
+ * but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old
+ * versions of this database that are no longer supported. If a database with older version that
+ * minimumSupportedVersion is found, it is simply deleted and a new database is created with the
+ * given name and version
+ *
+ * @param context to use to open or create the database
+ * @param name the name of the database file, null for a temporary in-memory database
+ * @param factory to use for creating cursor objects, null for default
+ * @param version the required version of the database
+ * @param minimumSupportedVersion the minimum version that is supported to be upgraded to
+ * {@code version} via {@link #onUpgrade}. If the current database version is lower
+ * than this, database is simply deleted and recreated with the version passed in
+ * {@code version}. {@link #onBeforeDelete} is called before deleting the database
+ * when this happens. This is 0 by default.
+ * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
+ * corruption, or null to use the default error handler.
+ * @see #onBeforeDelete(SQLiteDatabase)
+ * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)
+ * @see #onUpgrade(SQLiteDatabase, int, int)
+ * @hide
+ */
+ public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
+ int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
@@ -103,6 +133,7 @@
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
+ mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
}
/**
@@ -245,21 +276,34 @@
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
- db.beginTransaction();
- try {
- if (version == 0) {
- onCreate(db);
+ if (version > 0 && version < mMinimumSupportedVersion) {
+ File databaseFile = new File(db.getPath());
+ onBeforeDelete(db);
+ db.close();
+ if (SQLiteDatabase.deleteDatabase(databaseFile)) {
+ mIsInitializing = false;
+ return getDatabaseLocked(writable);
} else {
- if (version > mNewVersion) {
- onDowngrade(db, version, mNewVersion);
- } else {
- onUpgrade(db, version, mNewVersion);
- }
+ throw new IllegalStateException("Unable to delete obsolete database "
+ + mName + " with version " + version);
}
- db.setVersion(mNewVersion);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
+ } else {
+ db.beginTransaction();
+ try {
+ if (version == 0) {
+ onCreate(db);
+ } else {
+ if (version > mNewVersion) {
+ onDowngrade(db, version, mNewVersion);
+ } else {
+ onUpgrade(db, version, mNewVersion);
+ }
+ }
+ db.setVersion(mNewVersion);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
}
}
@@ -292,18 +336,18 @@
}
/**
- * Called when the database connection is being configured, to enable features
- * such as write-ahead logging or foreign key support.
+ * Called when the database connection is being configured, to enable features such as
+ * write-ahead logging or foreign key support.
* <p>
- * This method is called before {@link #onCreate}, {@link #onUpgrade},
- * {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify
- * the database except to configure the database connection as required.
- * </p><p>
- * This method should only call methods that configure the parameters of the
- * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
- * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled},
- * {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize},
- * or executing PRAGMA statements.
+ * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade}, or
+ * {@link #onOpen} are called. It should not modify the database except to configure the
+ * database connection as required.
+ * </p>
+ * <p>
+ * This method should only call methods that configure the parameters of the database
+ * connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
+ * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, {@link SQLiteDatabase#setLocale},
+ * {@link SQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
* </p>
*
* @param db The database.
@@ -311,6 +355,20 @@
public void onConfigure(SQLiteDatabase db) {}
/**
+ * Called before the database is deleted when the version returned by
+ * {@link SQLiteDatabase#getVersion()} is lower than the minimum supported version passed (if at
+ * all) while creating this helper. After the database is deleted, a fresh database with the
+ * given version is created. This will be followed by {@link #onConfigure(SQLiteDatabase)} and
+ * {@link #onCreate(SQLiteDatabase)} being called with a new SQLiteDatabase object
+ *
+ * @param db the database opened with this helper
+ * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, int, DatabaseErrorHandler)
+ * @hide
+ */
+ public void onBeforeDelete(SQLiteDatabase db) {
+ }
+
+ /**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
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/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 45fa15e..1a51acd 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -119,8 +119,14 @@
captureTime = timestamp / 1000000 + timeOffset;
}
+ // Create this fresh each time since the time zone may change while a long-running application
+ // is active.
+ final DateFormat dateTimeStampFormat =
+ new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+ dateTimeStampFormat.setTimeZone(TimeZone.getDefault());
+
// Format for metadata
- String formattedCaptureTime = sDateTimeStampFormat.format(captureTime);
+ String formattedCaptureTime = dateTimeStampFormat.format(captureTime);
nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(),
formattedCaptureTime);
@@ -467,13 +473,10 @@
private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd HH:mm:ss";
private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
- private static final DateFormat sDateTimeStampFormat =
- new SimpleDateFormat(TIFF_DATETIME_FORMAT);
private final Calendar mGPSTimeStampCalendar = Calendar
.getInstance(TimeZone.getTimeZone("UTC"));
static {
- sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
}
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/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index fafe116..45aeb4a 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -88,15 +88,13 @@
/* Returns true if the specified USB function is enabled. */
boolean isFunctionEnabled(String function);
- /* Sets the current USB function. */
- void setCurrentFunction(String function);
-
- /* Sets whether USB data (for example, MTP exposed pictures) should be made
- * available on the USB connection. Unlocking data should only be done with
- * user involvement, since exposing pictures or other data could leak sensitive
- * user information.
+ /* Sets the current USB function as well as whether USB data
+ * (for example, MTP exposed pictures) should be made available
+ * on the USB connection. Unlocking data should only be done with
+ * user involvement, since exposing pictures or other data could
+ * leak sensitive user information.
*/
- void setUsbDataUnlocked(boolean unlock);
+ void setCurrentFunction(String function, boolean usbDataUnlocked);
/* Allow USB debugging from the attached host. If alwaysAllow is true, add the
* the public key to list of host keys that the user has approved.
diff --git a/core/java/android/hardware/usb/UsbAccessory.java b/core/java/android/hardware/usb/UsbAccessory.java
index 2f9178c..4aeb40c 100644
--- a/core/java/android/hardware/usb/UsbAccessory.java
+++ b/core/java/android/hardware/usb/UsbAccessory.java
@@ -16,8 +16,11 @@
package android.hardware.usb;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
/**
* A class representing a USB accessory, which is an external hardware component
@@ -46,12 +49,12 @@
private static final String TAG = "UsbAccessory";
- private final String mManufacturer;
- private final String mModel;
- private final String mDescription;
- private final String mVersion;
- private final String mUri;
- private final String mSerial;
+ private final @NonNull String mManufacturer;
+ private final @NonNull String mModel;
+ private final @Nullable String mDescription;
+ private final @Nullable String mVersion;
+ private final @Nullable String mUri;
+ private final @Nullable String mSerial;
/** @hide */
public static final int MANUFACTURER_STRING = 0;
@@ -70,10 +73,11 @@
* UsbAccessory should only be instantiated by UsbService implementation
* @hide
*/
- public UsbAccessory(String manufacturer, String model, String description,
- String version, String uri, String serial) {
- mManufacturer = manufacturer;
- mModel = model;
+ public UsbAccessory(@NonNull String manufacturer, @NonNull String model,
+ @Nullable String description, @Nullable String version, @Nullable String uri,
+ @Nullable String serial) {
+ mManufacturer = Preconditions.checkNotNull(manufacturer);
+ mModel = Preconditions.checkNotNull(model);
mDescription = description;
mVersion = version;
mUri = uri;
@@ -85,12 +89,8 @@
* @hide
*/
public UsbAccessory(String[] strings) {
- mManufacturer = strings[MANUFACTURER_STRING];
- mModel = strings[MODEL_STRING];
- mDescription = strings[DESCRIPTION_STRING];
- mVersion = strings[VERSION_STRING];
- mUri = strings[URI_STRING];
- mSerial = strings[SERIAL_STRING];
+ this(strings[MANUFACTURER_STRING], strings[MODEL_STRING], strings[DESCRIPTION_STRING],
+ strings[VERSION_STRING], strings[URI_STRING], strings[SERIAL_STRING]);
}
/**
@@ -98,7 +98,7 @@
*
* @return the accessory manufacturer
*/
- public String getManufacturer() {
+ public @NonNull String getManufacturer() {
return mManufacturer;
}
@@ -107,25 +107,25 @@
*
* @return the accessory model
*/
- public String getModel() {
+ public @NonNull String getModel() {
return mModel;
}
/**
* Returns a user visible description of the accessory.
*
- * @return the accessory description
+ * @return the accessory description, or {@code null} if not set
*/
- public String getDescription() {
+ public @Nullable String getDescription() {
return mDescription;
}
/**
* Returns the version of the accessory.
*
- * @return the accessory version
+ * @return the accessory version, or {@code null} if not set
*/
- public String getVersion() {
+ public @Nullable String getVersion() {
return mVersion;
}
@@ -134,9 +134,9 @@
* This is an optional URI that might show information about the accessory
* or provide the option to download an application for the accessory
*
- * @return the accessory URI
+ * @return the accessory URI, or {@code null} if not set
*/
- public String getUri() {
+ public @Nullable String getUri() {
return mUri;
}
@@ -145,9 +145,9 @@
* This is an optional serial number that can be used to differentiate
* between individual accessories of the same model and manufacturer
*
- * @return the unique serial number
+ * @return the unique serial number, or {@code null} if not set
*/
- public String getSerial() {
+ public @Nullable String getSerial() {
return mSerial;
}
@@ -172,12 +172,10 @@
@Override
public int hashCode() {
- return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
- (mModel == null ? 0 : mModel.hashCode()) ^
+ return mManufacturer.hashCode() ^ mModel.hashCode() ^
(mDescription == null ? 0 : mDescription.hashCode()) ^
(mVersion == null ? 0 : mVersion.hashCode()) ^
- (mUri == null ? 0 : mUri.hashCode()) ^
- (mSerial == null ? 0 : mSerial.hashCode()));
+ (mUri == null ? 0 : mUri.hashCode()) ^ (mSerial == null ? 0 : mSerial.hashCode());
}
@Override
diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
index da5c128..a171570 100644
--- a/core/java/android/hardware/usb/UsbConfiguration.java
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -16,8 +16,11 @@
package android.hardware.usb;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
/**
* A class representing a configuration on a {@link UsbDevice}.
@@ -35,20 +38,20 @@
public class UsbConfiguration implements Parcelable {
private final int mId;
- private final String mName;
+ private final @Nullable String mName;
private final int mAttributes;
private final int mMaxPower;
- private Parcelable[] mInterfaces;
+
+ /** All interfaces for this config, only null during creation */
+ private @Nullable Parcelable[] mInterfaces;
/**
* Mask for "self-powered" bit in the configuration's attributes.
- * @see #getAttributes
*/
private static final int ATTR_SELF_POWERED = 1 << 6;
/**
* Mask for "remote wakeup" bit in the configuration's attributes.
- * @see #getAttributes
*/
private static final int ATTR_REMOTE_WAKEUP = 1 << 5;
@@ -56,7 +59,7 @@
* UsbConfiguration should only be instantiated by UsbService implementation
* @hide
*/
- public UsbConfiguration(int id, String name, int attributes, int maxPower) {
+ public UsbConfiguration(int id, @Nullable String name, int attributes, int maxPower) {
mId = id;
mName = name;
mAttributes = attributes;
@@ -76,9 +79,9 @@
/**
* Returns the configuration's name.
*
- * @return the configuration's name
+ * @return the configuration's name, or {@code null} if the property could not be read
*/
- public String getName() {
+ public @Nullable String getName() {
return mName;
}
@@ -125,7 +128,7 @@
*
* @return the interface
*/
- public UsbInterface getInterface(int index) {
+ public @NonNull UsbInterface getInterface(int index) {
return (UsbInterface)mInterfaces[index];
}
@@ -133,8 +136,8 @@
* Only used by UsbService implementation
* @hide
*/
- public void setInterfaces(Parcelable[] interfaces) {
- mInterfaces = interfaces;
+ public void setInterfaces(@NonNull Parcelable[] interfaces) {
+ mInterfaces = Preconditions.checkArrayElementsNotNull(interfaces, "interfaces");
}
@Override
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index 410d550..425a89d 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -16,8 +16,11 @@
package android.hardware.usb;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
/**
* This class represents a USB device attached to the android device with the android device
@@ -42,29 +45,31 @@
private static final String TAG = "UsbDevice";
private static final boolean DEBUG = false;
- private final String mName;
- private final String mManufacturerName;
- private final String mProductName;
- private final String mVersion;
- private final String mSerialNumber;
+ private final @NonNull String mName;
+ private final @Nullable String mManufacturerName;
+ private final @Nullable String mProductName;
+ private final @NonNull String mVersion;
+ private final @Nullable String mSerialNumber;
private final int mVendorId;
private final int mProductId;
private final int mClass;
private final int mSubclass;
private final int mProtocol;
- private Parcelable[] mConfigurations;
- // list of all interfaces on the device
- private UsbInterface[] mInterfaces;
+ /** All configurations for this device, only null during creation */
+ private @Nullable Parcelable[] mConfigurations;
+
+ /** All interfaces on the device. Initialized on first call to getInterfaceList */
+ private @Nullable UsbInterface[] mInterfaces;
/**
* UsbDevice should only be instantiated by UsbService implementation
* @hide
*/
- public UsbDevice(String name, int vendorId, int productId,
- int Class, int subClass, int protocol,
- String manufacturerName, String productName, String version, String serialNumber) {
- mName = name;
+ public UsbDevice(@NonNull String name, int vendorId, int productId, int Class, int subClass,
+ int protocol, @Nullable String manufacturerName, @Nullable String productName,
+ @NonNull String version, @Nullable String serialNumber) {
+ mName = Preconditions.checkNotNull(name);
mVendorId = vendorId;
mProductId = productId;
mClass = Class;
@@ -72,7 +77,7 @@
mProtocol = protocol;
mManufacturerName = manufacturerName;
mProductName = productName;
- mVersion = version;
+ mVersion = Preconditions.checkStringNotEmpty(version);
mSerialNumber = serialNumber;
}
@@ -83,25 +88,25 @@
*
* @return the device name
*/
- public String getDeviceName() {
+ public @NonNull String getDeviceName() {
return mName;
}
/**
* Returns the manufacturer name of the device.
*
- * @return the manufacturer name
+ * @return the manufacturer name, or {@code null} if the property could not be read
*/
- public String getManufacturerName() {
+ public @Nullable String getManufacturerName() {
return mManufacturerName;
}
/**
* Returns the product name of the device.
*
- * @return the product name
+ * @return the product name, or {@code null} if the property could not be read
*/
- public String getProductName() {
+ public @Nullable String getProductName() {
return mProductName;
}
@@ -110,16 +115,16 @@
*
* @return the device version
*/
- public String getVersion() {
+ public @NonNull String getVersion() {
return mVersion;
}
/**
* Returns the serial number of the device.
*
- * @return the serial number name
+ * @return the serial number name, or {@code null} if the property could not be read
*/
- public String getSerialNumber() {
+ public @Nullable String getSerialNumber() {
return mSerialNumber;
}
@@ -195,11 +200,11 @@
*
* @return the configuration
*/
- public UsbConfiguration getConfiguration(int index) {
+ public @NonNull UsbConfiguration getConfiguration(int index) {
return (UsbConfiguration)mConfigurations[index];
}
- private UsbInterface[] getInterfaceList() {
+ private @Nullable UsbInterface[] getInterfaceList() {
if (mInterfaces == null) {
int configurationCount = mConfigurations.length;
int interfaceCount = 0;
@@ -240,7 +245,7 @@
*
* @return the interface
*/
- public UsbInterface getInterface(int index) {
+ public @NonNull UsbInterface getInterface(int index) {
return getInterfaceList()[index];
}
@@ -248,8 +253,8 @@
* Only used by UsbService implementation
* @hide
*/
- public void setConfigurations(Parcelable[] configuration) {
- mConfigurations = configuration;
+ public void setConfigurations(@NonNull Parcelable[] configuration) {
+ mConfigurations = Preconditions.checkArrayElementsNotNull(configuration, "configuration");
}
@Override
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index f7bf1e4..ca2b1f8 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -21,10 +21,13 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.os.ParcelFileDescriptor;
+
+import com.android.internal.util.Preconditions;
+
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
-
+import java.nio.ByteBuffer;
/**
* This class is used for sending and receiving data and control messages to a USB device.
@@ -257,16 +260,56 @@
/**
* Waits for the result of a {@link android.hardware.usb.UsbRequest#queue} operation
- * Note that this may return requests queued on multiple
- * {@link android.hardware.usb.UsbEndpoint}s.
- * When multiple endpoints are in use, {@link android.hardware.usb.UsbRequest#getEndpoint} and
- * {@link android.hardware.usb.UsbRequest#getClientData} can be useful in determining
- * how to process the result of this function.
+ * <p>Note that this may return requests queued on multiple
+ * {@link android.hardware.usb.UsbEndpoint}s. When multiple endpoints are in use,
+ * {@link android.hardware.usb.UsbRequest#getEndpoint} and {@link
+ * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process
+ * the result of this function.</p>
+ * <p>Position and array offset of the request's buffer are ignored and assumed to be 0. The
+ * position will be set to the number of bytes read/written.</p>
*
* @return a completed USB request, or null if an error occurred
+ *
+ * @throws IllegalArgumentException if the number of bytes read or written is more than the
+ * limit of the request's buffer. The number of bytes is
+ * determined by the {@code length} parameter of
+ * {@link UsbRequest#queue(ByteBuffer, int)}
*/
public UsbRequest requestWait() {
- UsbRequest request = native_request_wait();
+ // -1 is special value indicating infinite wait
+ UsbRequest request = native_request_wait(-1);
+ if (request != null) {
+ request.dequeue();
+ }
+ return request;
+ }
+
+ /**
+ * Waits for the result of a {@link android.hardware.usb.UsbRequest#queue} operation
+ * <p>Note that this may return requests queued on multiple
+ * {@link android.hardware.usb.UsbEndpoint}s. When multiple endpoints are in use,
+ * {@link android.hardware.usb.UsbRequest#getEndpoint} and {@link
+ * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process
+ * the result of this function.</p>
+ * <p>Position and array offset of the request's buffer are ignored and assumed to be 0. The
+ * position will be set to the number of bytes read/written.</p>
+ * <p>Android processes {@link UsbRequest UsbRequests} asynchronously. Hence it is not
+ * guaranteed that {@link #requestWait(int) requestWait(0)} returns a request that has been
+ * queued right before even if the request could have been processed immediately.</p>
+ *
+ * @param timeout timeout in milliseconds. If 0 this method does not wait.
+ *
+ * @return a completed USB request, or {@code null} if an error or time out occurred
+ *
+ * @throws IllegalArgumentException if the number of bytes read or written is more than the
+ * limit of the request's buffer. The number of bytes is
+ * determined by the {@code length} parameter of
+ * {@link UsbRequest#queue(ByteBuffer, int)}
+ */
+ public UsbRequest requestWait(int timeout) {
+ timeout = Preconditions.checkArgumentNonnegative(timeout);
+
+ UsbRequest request = native_request_wait(timeout);
if (request != null) {
request.dequeue();
}
@@ -311,7 +354,7 @@
int index, byte[] buffer, int offset, int length, int timeout);
private native int native_bulk_request(int endpoint, byte[] buffer,
int offset, int length, int timeout);
- private native UsbRequest native_request_wait();
+ private native UsbRequest native_request_wait(int timeout);
private native String native_get_serial();
private native boolean native_reset_device();
}
diff --git a/core/java/android/hardware/usb/UsbInterface.java b/core/java/android/hardware/usb/UsbInterface.java
index de01a88..4b5278c 100644
--- a/core/java/android/hardware/usb/UsbInterface.java
+++ b/core/java/android/hardware/usb/UsbInterface.java
@@ -16,8 +16,10 @@
package android.hardware.usb;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
/**
* A class representing an interface on a {@link UsbDevice}.
@@ -36,17 +38,19 @@
private final int mId;
private final int mAlternateSetting;
- private final String mName;
+ private @Nullable final String mName;
private final int mClass;
private final int mSubclass;
private final int mProtocol;
+
+ /** All endpoints of this interface, only null during creation */
private Parcelable[] mEndpoints;
/**
* UsbInterface should only be instantiated by UsbService implementation
* @hide
*/
- public UsbInterface(int id, int alternateSetting, String name,
+ public UsbInterface(int id, int alternateSetting, @Nullable String name,
int Class, int subClass, int protocol) {
mId = id;
mAlternateSetting = alternateSetting;
@@ -83,9 +87,9 @@
/**
* Returns the interface's name.
*
- * @return the interface's name
+ * @return the interface's name, or {@code null} if the property could not be read
*/
- public String getName() {
+ public @Nullable String getName() {
return mName;
}
@@ -140,7 +144,7 @@
* @hide
*/
public void setEndpoints(Parcelable[] endpoints) {
- mEndpoints = endpoints;
+ mEndpoints = Preconditions.checkArrayElementsNotNull(endpoints, "endpoints");
}
@Override
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 1d20c78..3636e4e 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -542,33 +542,23 @@
* {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
* or {@link #USB_FUNCTION_RNDIS}.
* </p><p>
+ * Also sets whether USB data (for example, MTP exposed pictures) should be made available
+ * on the USB connection when in device mode. Unlocking usb data should only be done with
+ * user involvement, since exposing pictures or other data could leak sensitive
+ * user information.
+ * </p><p>
* Note: This function is asynchronous and may fail silently without applying
* the requested changes.
* </p>
*
* @param function name of the USB function, or null to restore the default function
+ * @param usbDataUnlocked whether user data is accessible
*
* {@hide}
*/
- public void setCurrentFunction(String function) {
+ public void setCurrentFunction(String function, boolean usbDataUnlocked) {
try {
- mService.setCurrentFunction(function);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Sets whether USB data (for example, MTP exposed pictures) should be made available
- * on the USB connection when in device mode. Unlocking usb data should only be done with
- * user involvement, since exposing pictures or other data could leak sensitive
- * user information.
- *
- * {@hide}
- */
- public void setUsbDataUnlocked(boolean unlocked) {
- try {
- mService.setUsbDataUnlocked(unlocked);
+ mService.setCurrentFunction(function, usbDataUnlocked);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index b531e5f..9f7592f 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -17,6 +17,7 @@
package android.hardware.usb;
import android.util.Log;
+import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
import java.nio.ByteBuffer;
@@ -66,7 +67,7 @@
*/
public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) {
mEndpoint = endpoint;
- mConnection = connection;
+ mConnection = Preconditions.checkNotNull(connection);
boolean wasInitialized = native_init(connection, endpoint.getAddress(),
endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval());
@@ -137,15 +138,16 @@
/**
* Queues the request to send or receive data on its endpoint.
- * For OUT endpoints, the given buffer data will be sent on the endpoint.
- * For IN endpoints, the endpoint will attempt to read the given number of bytes
- * into the specified buffer.
- * If the queueing operation is successful, we return true and the result will be
- * returned via {@link android.hardware.usb.UsbDeviceConnection#requestWait}
+ * <p>For OUT endpoints, the given buffer data will be sent on the endpoint. For IN endpoints,
+ * the endpoint will attempt to read the given number of bytes into the specified buffer. If the
+ * queueing operation is successful, we return true and the result will be returned via {@link
+ * android.hardware.usb.UsbDeviceConnection#requestWait}</p>
*
- * @param buffer the buffer containing the bytes to write, or location to store
- * the results of a read
- * @param length number of bytes to read or write
+ * @param buffer the buffer containing the bytes to write, or location to store the results of a
+ * read. Position and array offset will be ignored and assumed to be 0. Limit and
+ * capacity will be ignored.
+ * @param length number of bytes to read or write.
+ *
* @return true if the queueing operation succeeded
*/
public boolean queue(ByteBuffer buffer, int length) {
@@ -155,6 +157,9 @@
mBuffer = buffer;
mLength = length;
+ // Note: On a buffer slice we lost the capacity information about the underlying buffer,
+ // hence we cannot check if the access would be a data leak/memory corruption.
+
boolean result;
if (buffer.isDirect()) {
result = native_queue_direct(buffer, length, out);
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 ea53a71..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;
}
@@ -124,6 +124,10 @@
return false;
}
+ public boolean isUp() {
+ return hasFlag(FLAG_UP);
+ }
+
/** {@inheritDoc} */
public int describeContents() {
return 0;
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index 59c5fb6..c3abcf7 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -50,9 +50,13 @@
public static final int DHCP_INVALID_OPTION_LENGTH = makeErrorCode(DHCP_ERROR, 3);
public static final int DHCP_NO_MSG_TYPE = makeErrorCode(DHCP_ERROR, 4);
public static final int DHCP_UNKNOWN_MSG_TYPE = makeErrorCode(DHCP_ERROR, 5);
+ /** {@hide} */
+ public static final int DHCP_NO_COOKIE = makeErrorCode(DHCP_ERROR, 6);
public static final int BUFFER_UNDERFLOW = makeErrorCode(MISC_ERROR, 1);
public static final int RECEIVE_ERROR = makeErrorCode(MISC_ERROR, 2);
+ /** {@hide} */
+ public static final int PARSING_ERROR = makeErrorCode(MISC_ERROR, 3);
public final String ifName;
// error code byte format (MSB to LSB):
@@ -115,8 +119,9 @@
}
final static class Decoder {
- static final SparseArray<String> constants =
- MessageUtils.findMessageNames(new Class[]{DhcpErrorEvent.class},
- new String[]{"L2_", "L3_", "L4_", "BOOTP_", "DHCP_", "BUFFER_", "RECEIVE_"});
+ static final SparseArray<String> constants = MessageUtils.findMessageNames(
+ new Class[]{DhcpErrorEvent.class},
+ new String[]{"L2_", "L3_", "L4_", "BOOTP_", "DHCP_", "BUFFER_", "RECEIVE_",
+ "PARSING_"});
}
}
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 331cf0c..1a31b56 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -34,10 +34,12 @@
@SystemApi
public final class ValidationProbeEvent implements Parcelable {
- public static final int PROBE_DNS = 0;
- public static final int PROBE_HTTP = 1;
- public static final int PROBE_HTTPS = 2;
- public static final int PROBE_PAC = 3;
+ public static final int PROBE_DNS = 0;
+ public static final int PROBE_HTTP = 1;
+ public static final int PROBE_HTTPS = 2;
+ public static final int PROBE_PAC = 3;
+ /** {@hide} */
+ public static final int PROBE_FALLBACK = 4;
public static final int DNS_FAILURE = 0;
public static final int DNS_SUCCESS = 1;
@@ -57,7 +59,7 @@
public final @ProbeType int probeType;
public final @ReturnCode int returnCode;
- /** @hide */
+ /** {@hide} */
public ValidationProbeEvent(
int netId, long durationMs, @ProbeType int probeType, @ReturnCode int returnCode) {
this.netId = netId;
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 9a4b599..fea64ec 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -304,6 +304,9 @@
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
+ } catch (Throwable tr) {
+ mCancelled.set(true);
+ throw tr;
} finally {
postResult(result);
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 95dd148..3d5d900 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -5433,7 +5433,8 @@
if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
if (dumpDurationSteps(pw, " ", "Discharge step durations:",
getDischargeLevelStepTracker(), false)) {
- long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
+ long timeRemaining = computeBatteryTimeRemaining(
+ SystemClock.elapsedRealtime() * 1000);
if (timeRemaining >= 0) {
pw.print(" Estimated discharge time remaining: ");
TimeUtils.formatDuration(timeRemaining / 1000, pw);
@@ -5449,7 +5450,8 @@
}
if (dumpDurationSteps(pw, " ", "Charge step durations:",
getChargeLevelStepTracker(), false)) {
- long timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime());
+ long timeRemaining = computeChargeTimeRemaining(
+ SystemClock.elapsedRealtime() * 1000);
if (timeRemaining >= 0) {
pw.print(" Estimated charge time remaining: ");
TimeUtils.formatDuration(timeRemaining / 1000, pw);
@@ -5612,14 +5614,14 @@
if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
String[] lineArgs = new String[1];
- long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
+ long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime() * 1000);
if (timeRemaining >= 0) {
lineArgs[0] = Long.toString(timeRemaining);
dumpLine(pw, 0 /* uid */, "i" /* category */, DISCHARGE_TIME_REMAIN_DATA,
(Object[])lineArgs);
}
dumpDurationSteps(pw, "", CHARGE_STEP_DATA, getChargeLevelStepTracker(), true);
- timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime());
+ timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime() * 1000);
if (timeRemaining >= 0) {
lineArgs[0] = Long.toString(timeRemaining);
dumpLine(pw, 0 /* uid */, "i" /* category */, CHARGE_TIME_REMAIN_DATA,
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ea8ba2f..cf77567 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -23,7 +23,6 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
@@ -361,13 +360,14 @@
ParcelFileDescriptor out = data.readFileDescriptor();
ParcelFileDescriptor err = data.readFileDescriptor();
String[] args = data.readStringArray();
+ ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
try {
if (out != null) {
shellCommand(in != null ? in.getFileDescriptor() : null,
out.getFileDescriptor(),
err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
- args, resultReceiver);
+ args, shellCallback, resultReceiver);
}
} finally {
IoUtils.closeQuietly(in);
@@ -459,13 +459,15 @@
* @param out The raw file descriptor that normal command messages should be written to.
* @param err The raw file descriptor that command error messages should be written to.
* @param args Command-line arguments.
+ * @param callback Callback through which to interact with the invoking shell.
* @param resultReceiver Called when the command has finished executing, with the result code.
* @throws RemoteException
* @hide
*/
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
- onShellCommand(in, out, err, args, resultReceiver);
+ String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) throws RemoteException {
+ onShellCommand(in, out, err, args, callback, resultReceiver);
}
/**
@@ -477,7 +479,7 @@
* @hide
*/
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException {
FileOutputStream fout = new FileOutputStream(err != null ? err : out);
PrintWriter pw = new FastPrintWriter(fout);
pw.println("No shell command implementation.");
@@ -650,13 +652,15 @@
}
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeFileDescriptor(in);
data.writeFileDescriptor(out);
data.writeFileDescriptor(err);
data.writeStringArray(args);
+ ShellCallback.writeToParcel(callback, data);
resultReceiver.writeToParcel(data, 0);
try {
transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 7061589..d04d6c2 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -329,25 +329,49 @@
* Filter values in Bundle to only basic types.
* @hide
*/
- public void filterValues() {
+ public Bundle filterValues() {
unparcel();
+ Bundle bundle = this;
if (mMap != null) {
- for (int i = mMap.size() - 1; i >= 0; i--) {
- Object value = mMap.valueAt(i);
+ ArrayMap<String, Object> map = mMap;
+ for (int i = map.size() - 1; i >= 0; i--) {
+ Object value = map.valueAt(i);
if (PersistableBundle.isValidType(value)) {
continue;
}
if (value instanceof Bundle) {
- ((Bundle)value).filterValues();
+ Bundle newBundle = ((Bundle)value).filterValues();
+ if (newBundle != value) {
+ if (map == mMap) {
+ // The filter had to generate a new bundle, but we have not yet
+ // created a new one here. Do that now.
+ bundle = new Bundle(this);
+ // Note the ArrayMap<> constructor is guaranteed to generate
+ // a new object with items in the same order as the original.
+ map = bundle.mMap;
+ }
+ // Replace this current entry with the new child bundle.
+ map.setValueAt(i, newBundle);
+ }
+ continue;
}
if (value.getClass().getName().startsWith("android.")) {
continue;
}
- mMap.removeAt(i);
+ if (map == mMap) {
+ // This is the first time we have had to remove something, that means we
+ // need to switch to a new Bundle.
+ bundle = new Bundle(this);
+ // Note the ArrayMap<> constructor is guaranteed to generate
+ // a new object with items in the same order as the original.
+ map = bundle.mMap;
+ }
+ map.removeAt(i);
}
}
mFlags |= FLAG_HAS_FDS_KNOWN;
mFlags &= ~FLAG_HAS_FDS;
+ return bundle;
}
/**
diff --git a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java b/core/java/android/os/Debug.aidl
similarity index 69%
copy from core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
copy to core/java/android/os/Debug.aidl
index fb76e67..81a2c1f 100644
--- a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
+++ b/core/java/android/os/Debug.aidl
@@ -14,15 +14,6 @@
* limitations under the License.
*/
-package android.print.mockservice;
+package android.os;
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
+parcelable Debug.MemoryInfo;
\ No newline at end of file
diff --git a/core/java/android/os/HardwarePropertiesManager.java b/core/java/android/os/HardwarePropertiesManager.java
index 9d362d6..6dec0dc 100644
--- a/core/java/android/os/HardwarePropertiesManager.java
+++ b/core/java/android/os/HardwarePropertiesManager.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.thermal.V1_0.Constants;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -56,18 +57,19 @@
/**
* Device temperature types. These must match the values in
* frameworks/native/include/hardwareproperties/HardwarePropertiesManager.h
+ * TODO(b/32022261) Remove this comment.
*/
/** Temperature of CPUs in Celsius. */
- public static final int DEVICE_TEMPERATURE_CPU = 0;
+ public static final int DEVICE_TEMPERATURE_CPU = Constants.TemperatureType.CPU;
/** Temperature of GPUs in Celsius. */
- public static final int DEVICE_TEMPERATURE_GPU = 1;
+ public static final int DEVICE_TEMPERATURE_GPU = Constants.TemperatureType.GPU;
/** Temperature of battery in Celsius. */
- public static final int DEVICE_TEMPERATURE_BATTERY = 2;
+ public static final int DEVICE_TEMPERATURE_BATTERY = Constants.TemperatureType.BATTERY;
/** Temperature of device skin in Celsius. */
- public static final int DEVICE_TEMPERATURE_SKIN = 3;
+ public static final int DEVICE_TEMPERATURE_SKIN = Constants.TemperatureType.SKIN;
/** Get current temperature. */
public static final int TEMPERATURE_CURRENT = 0;
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 5ff79f7..0e7da63 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -38,8 +38,11 @@
public abstract void onTransact(
int code, HwParcel request, HwParcel reply, int flags);
- public native final void registerService(String serviceName);
- public static native final IHwBinder getService(String serviceName);
+ public native final void registerService(
+ String serviceName, int versionMajor, int versionMinor);
+
+ public static native final IHwBinder getService(
+ String serviceName, int versionMajor, int versionMinor);
// Returns address of the "freeFunction".
private static native final long native_init();
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/IBinder.java b/core/java/android/os/IBinder.java
index 0fa8750..f762a05 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -220,11 +220,13 @@
* @param out The raw file descriptor that normal command messages should be written to.
* @param err The raw file descriptor that command error messages should be written to.
* @param args Command-line arguments.
+ * @param shellCallback Optional callback to the caller's shell to perform operations in it.
* @param resultReceiver Called when the command has finished executing, with the result code.
* @hide
*/
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException;
+ String[] args, ShellCallback shellCallback,
+ ResultReceiver resultReceiver) throws RemoteException;
/**
* Perform a generic operation with the object.
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index 12830a4..c5ceecd 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -25,4 +25,5 @@
boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
boolean setupBcb(in String command);
boolean clearBcb();
+ void rebootRecoveryWithCommand(in String command);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index eeb641d..65c60930 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -36,7 +36,8 @@
int getCredentialOwnerProfile(int userHandle);
UserInfo createUser(in String name, int flags);
- UserInfo createProfileForUser(in String name, int flags, int userHandle);
+ UserInfo createProfileForUser(in String name, int flags, int userHandle,
+ in String[] disallowedPackages);
UserInfo createRestrictedProfile(String name, int parentUserHandle);
void setUserEnabled(int userHandle);
boolean removeUser(int userHandle);
@@ -82,4 +83,7 @@
boolean someUserHasSeedAccount(in String accountName, in String accountType);
boolean isManagedProfile(int userId);
boolean isDemoUser(int userId);
+ UserInfo createProfileForUserEvenWhenDisallowed(in String name, int flags, int userHandle,
+ in String[] disallowedPackages);
+ boolean isUserUnlockingOrUnlocked(int userId);
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f6e6ad6..85f999b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -46,6 +46,7 @@
import java.util.Map;
import java.util.Set;
+import dalvik.annotation.optimization.FastNative;
import dalvik.system.VMRuntime;
/**
@@ -254,22 +255,35 @@
// see libbinder's binder/Status.h
private static final int EX_TRANSACTION_FAILED = -129;
+ @FastNative
private static native int nativeDataSize(long nativePtr);
+ @FastNative
private static native int nativeDataAvail(long nativePtr);
+ @FastNative
private static native int nativeDataPosition(long nativePtr);
+ @FastNative
private static native int nativeDataCapacity(long nativePtr);
+ @FastNative
private static native long nativeSetDataSize(long nativePtr, int size);
+ @FastNative
private static native void nativeSetDataPosition(long nativePtr, int pos);
+ @FastNative
private static native void nativeSetDataCapacity(long nativePtr, int size);
+ @FastNative
private static native boolean nativePushAllowFds(long nativePtr, boolean allowFds);
+ @FastNative
private static native void nativeRestoreAllowFds(long nativePtr, boolean lastValue);
private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len);
private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len);
+ @FastNative
private static native void nativeWriteInt(long nativePtr, int val);
+ @FastNative
private static native void nativeWriteLong(long nativePtr, long val);
+ @FastNative
private static native void nativeWriteFloat(long nativePtr, float val);
+ @FastNative
private static native void nativeWriteDouble(long nativePtr, double val);
private static native void nativeWriteString(long nativePtr, String val);
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
@@ -277,9 +291,13 @@
private static native byte[] nativeCreateByteArray(long nativePtr);
private static native byte[] nativeReadBlob(long nativePtr);
+ @FastNative
private static native int nativeReadInt(long nativePtr);
+ @FastNative
private static native long nativeReadLong(long nativePtr);
+ @FastNative
private static native float nativeReadFloat(long nativePtr);
+ @FastNative
private static native double nativeReadDouble(long nativePtr);
private static native String nativeReadString(long nativePtr);
private static native IBinder nativeReadStrongBinder(long nativePtr);
@@ -294,6 +312,7 @@
long nativePtr, byte[] data, int offset, int length);
private static native long nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int offset, int length);
+ @FastNative
private static native boolean nativeHasFileDescriptors(long nativePtr);
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 9bbe8f9..c153184 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -53,24 +53,6 @@
*/
public static final int WAKEFULNESS_DOZING = 3;
-
- /**
- * Power hint:
- * Interaction: The user is interacting with the device. The corresponding data field must be
- * the expected duration of the interaction, or 0 if unknown.
- *
- * Sustained Performance Mode: The corresponding data field must be Enable/Disable
- * Sustained Performance Mode.
- *
- * Launch: This is specific for activity launching. The corresponding data field must be
- * the expected duration of the required boost, or 0 if unknown.
- *
- * These must be kept in sync with the values in hardware/libhardware/include/hardware/power.h
- */
- public static final int POWER_HINT_INTERACTION = 2;
- public static final int POWER_HINT_SUSTAINED_PERFORMANCE_MODE = 6;
- public static final int POWER_HINT_LAUNCH = 8;
-
public static String wakefulnessToString(int wakefulness) {
switch (wakefulness) {
case WAKEFULNESS_ASLEEP:
@@ -169,5 +151,9 @@
public abstract void uidGone(int uid);
+ /**
+ * The hintId sent through this method should be in-line with the
+ * PowerHint defined in android/hardware/power/<version 1.0 & up>/IPower.h
+ */
public abstract void powerHint(int hintId, int data);
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e9ebe2d..4eee854 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -17,11 +17,10 @@
package android.os;
import android.annotation.TestApi;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
+import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
/**
@@ -135,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.
@@ -427,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/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 90bd11f..d48431a 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -93,6 +93,14 @@
*/
public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
+ /**
+ * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure)
+ * of uncrypt.
+ *
+ * @hide
+ */
+ public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status");
+
// Length limits for reading files.
private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
@@ -692,28 +700,22 @@
* @throws IOException if something goes wrong.
*/
private static void bootCommand(Context context, String... args) throws IOException {
- synchronized (sRequestLock) {
- LOG_FILE.delete();
+ LOG_FILE.delete();
- StringBuilder command = new StringBuilder();
- for (String arg : args) {
- if (!TextUtils.isEmpty(arg)) {
- command.append(arg);
- command.append("\n");
- }
+ StringBuilder command = new StringBuilder();
+ for (String arg : args) {
+ if (!TextUtils.isEmpty(arg)) {
+ command.append(arg);
+ command.append("\n");
}
-
- // Write the command into BCB (bootloader control block).
- RecoverySystem rs = (RecoverySystem) context.getSystemService(
- Context.RECOVERY_SERVICE);
- rs.setupBcb(command.toString());
-
- // Having set up the BCB, go ahead and reboot.
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- pm.reboot(PowerManager.REBOOT_RECOVERY);
-
- throw new IOException("Reboot failed (no permissions?)");
}
+
+ // Write the command into BCB (bootloader control block) and boot from
+ // there. Will not return unless failed.
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ rs.rebootRecoveryWithCommand(command.toString());
+
+ throw new IOException("Reboot failed (no permissions?)");
}
// Read last_install; then report time (in seconds) and I/O (in MiB) for
@@ -908,6 +910,17 @@
}
/**
+ * Talks to RecoverySystemService via Binder to set up the BCB command and
+ * reboot into recovery accordingly.
+ */
+ private void rebootRecoveryWithCommand(String command) {
+ try {
+ mService.rebootRecoveryWithCommand(command);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
* Internally, recovery treats each line of the command file as a separate
* argv, so we only need to protect against newlines and nulls.
*/
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
new file mode 100644
index 0000000..e7fe697
--- /dev/null
+++ b/core/java/android/os/ShellCallback.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import android.util.Log;
+
+import com.android.internal.os.IShellCallback;
+
+/**
+ * Special-purpose API for use with {@link IBinder#shellCommand IBinder.shellCommand} for
+ * performing operations back on the invoking shell.
+ * @hide
+ */
+public class ShellCallback implements Parcelable {
+ final static String TAG = "ShellCallback";
+
+ final static boolean DEBUG = false;
+
+ final boolean mLocal;
+
+ IShellCallback mShellCallback;
+
+ class MyShellCallback extends IShellCallback.Stub {
+ public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
+ return onOpenOutputFile(path, seLinuxContext);
+ }
+ }
+
+ /**
+ * Create a new ShellCallback to receive requests.
+ */
+ public ShellCallback() {
+ mLocal = true;
+ }
+
+ /**
+ * Ask the shell to open a file for writing. This will truncate the file if it
+ * already exists. It will create the file if it doesn't exist.
+ * @param path Path of the file to be opened/created.
+ * @param seLinuxContext Optional SELinux context that must be allowed to have
+ * access to the file; if null, nothing is required.
+ */
+ public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
+ if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+ + " mShellCallback=" + mShellCallback);
+
+ if (mLocal) {
+ return onOpenOutputFile(path, seLinuxContext);
+ }
+
+ if (mShellCallback != null) {
+ try {
+ return mShellCallback.openOutputFile(path, seLinuxContext);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure opening " + path, e);
+ }
+ }
+ return null;
+ }
+
+ public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ return null;
+ }
+
+ public static void writeToParcel(ShellCallback callback, Parcel out) {
+ if (callback == null) {
+ out.writeStrongBinder(null);
+ } else {
+ callback.writeToParcel(out, 0);
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ synchronized (this) {
+ if (mShellCallback == null) {
+ mShellCallback = new MyShellCallback();
+ }
+ out.writeStrongBinder(mShellCallback.asBinder());
+ }
+ }
+
+ ShellCallback(Parcel in) {
+ mLocal = false;
+ mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+ }
+
+ public static final Parcelable.Creator<ShellCallback> CREATOR
+ = new Parcelable.Creator<ShellCallback>() {
+ public ShellCallback createFromParcel(Parcel in) {
+ return new ShellCallback(in);
+ }
+ public ShellCallback[] newArray(int size) {
+ return new ShellCallback[size];
+ }
+ };
+}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index fc804e5..e4a12e8 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -40,6 +40,7 @@
private FileDescriptor mOut;
private FileDescriptor mErr;
private String[] mArgs;
+ private ShellCallback mShellCallback;
private ResultReceiver mResultReceiver;
private String mCmd;
@@ -55,12 +56,13 @@
private InputStream mInputStream;
public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, int firstArgPos) {
+ String[] args, ShellCallback callback, int firstArgPos) {
mTarget = target;
mIn = in;
mOut = out;
mErr = err;
mArgs = args;
+ mShellCallback = callback;
mResultReceiver = null;
mCmd = null;
mArgPos = firstArgPos;
@@ -74,7 +76,7 @@
}
public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
String cmd;
int start;
if (args != null && args.length > 0) {
@@ -84,7 +86,7 @@
cmd = null;
start = 0;
}
- init(target, in, out, err, args, start);
+ init(target, in, out, err, args, callback, start);
mCmd = cmd;
mResultReceiver = resultReceiver;
@@ -105,7 +107,7 @@
// go.
PrintWriter eout = getErrPrintWriter();
eout.println();
- eout.println("Exception occurred while dumping:");
+ eout.println("Exception occurred while executing:");
e.printStackTrace(eout);
} finally {
if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
@@ -186,6 +188,25 @@
}
/**
+ * Helper for just system services to ask the shell to open an output file.
+ * @hide
+ */
+ public ParcelFileDescriptor openOutputFileForSystem(String path) {
+ try {
+ ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
+ "u:r:system_server:s0");
+ if (pfd != null) {
+ return pfd;
+ }
+ } catch (RuntimeException e) {
+ getErrPrintWriter().println("Failure opening file: " + e.getMessage());
+ }
+ getErrPrintWriter().println("Error: Unable to open file: " + path);
+ getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
+ return null;
+ }
+
+ /**
* Return the next option on the command line -- that is an argument that
* starts with '-'. If the next argument is not an option, null is returned.
*/
@@ -257,6 +278,13 @@
return arg;
}
+ /**
+ * Return the {@link ShellCallback} for communicating back with the calling shell.
+ */
+ public ShellCallback getShellCallback() {
+ return mShellCallback;
+ }
+
public int handleDefaultCommands(String cmd) {
if ("dump".equals(cmd)) {
String[] newArgs = new String[mArgs.length-1];
@@ -274,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/SystemClock.java b/core/java/android/os/SystemClock.java
index 672df6d..b3d76d7 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -20,6 +20,8 @@
import android.content.Context;
import android.util.Slog;
+import dalvik.annotation.optimization.CriticalNative;
+
/**
* Core timekeeping facilities.
*
@@ -124,7 +126,7 @@
}
duration = start + ms - uptimeMillis();
} while (duration > 0);
-
+
if (interrupted) {
// Important: we don't want to quietly eat an interrupt() event,
// so we make sure to re-interrupt the thread so that the next
@@ -132,7 +134,7 @@
Thread.currentThread().interrupt();
}
}
-
+
/**
* Sets the current wall time, in milliseconds. Requires the calling
* process to have appropriate permissions.
@@ -162,6 +164,7 @@
*
* @return milliseconds of non-sleep uptime since boot.
*/
+ @CriticalNative
native public static long uptimeMillis();
/**
@@ -169,6 +172,7 @@
*
* @return elapsed milliseconds since boot.
*/
+ @CriticalNative
native public static long elapsedRealtime();
/**
@@ -176,30 +180,34 @@
*
* @return elapsed nanoseconds since boot.
*/
+ @CriticalNative
public static native long elapsedRealtimeNanos();
/**
* Returns milliseconds running in the current thread.
- *
+ *
* @return elapsed milliseconds in the thread
*/
+ @CriticalNative
public static native long currentThreadTimeMillis();
/**
* Returns microseconds running in the current thread.
- *
+ *
* @return elapsed microseconds in the thread
- *
+ *
* @hide
*/
+ @CriticalNative
public static native long currentThreadTimeMicro();
/**
* Returns current wall time in microseconds.
- *
+ *
* @return elapsed microseconds in wall time
- *
+ *
* @hide
*/
+ @CriticalNative
public static native long currentTimeMicro();
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 3ae28fd..7e8cc0b8 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,6 +16,8 @@
package android.os;
+import dalvik.annotation.optimization.FastNative;
+
/**
* Writes trace events to the system trace buffer. These trace events can be
* collected and visualized using the Systrace tool.
@@ -91,14 +93,20 @@
private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
private static native long nativeGetEnabledTags();
- private static native void nativeTraceCounter(long tag, String name, int value);
- private static native void nativeTraceBegin(long tag, String name);
- private static native void nativeTraceEnd(long tag);
- private static native void nativeAsyncTraceBegin(long tag, String name, int cookie);
- private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
private static native void nativeSetAppTracingAllowed(boolean allowed);
private static native void nativeSetTracingEnabled(boolean allowed);
+ @FastNative
+ private static native void nativeTraceCounter(long tag, String name, int value);
+ @FastNative
+ private static native void nativeTraceBegin(long tag, String name);
+ @FastNative
+ private static native void nativeTraceEnd(long tag);
+ @FastNative
+ private static native void nativeAsyncTraceBegin(long tag, String name, int cookie);
+ @FastNative
+ private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
+
static {
// We configure two separate change callbacks, one in Trace.cpp and one here. The
// native callback reads the tags from the system property, and this callback
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5dc18fb..bc5af81 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1094,10 +1094,8 @@
/** {@hide} */
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
- // TODO Switch to using UMS internal isUserUnlockingOrUnlocked
try {
- return ActivityManagerNative.getDefault().isUserRunning(userId,
- ActivityManager.FLAG_AND_UNLOCKING_OR_UNLOCKED);
+ return mService.isUserUnlockingOrUnlocked(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1340,15 +1338,52 @@
*
* @param name the user's name
* @param flags flags that identify the type of user and other properties.
- * @see UserInfo
- * @param userHandle new user will be a profile of this use.
+ * @param userHandle new user will be a profile of this user.
*
- * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @return the {@link UserInfo} object for the created user, or null if the user
+ * could not be created.
* @hide
*/
public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle) {
+ return createProfileForUser(name, flags, userHandle, null);
+ }
+
+ /**
+ * Version of {@link #createProfileForUser(String, int, int)} that allows you to specify
+ * any packages that should not be installed in the new profile by default, these packages can
+ * still be installed later by the user if needed.
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @param userHandle new user will be a profile of this user.
+ * @param disallowedPackages packages that will not be installed in the profile being created.
+ *
+ * @return the {@link UserInfo} object for the created user, or null if the user
+ * could not be created.
+ * @hide
+ */
+ public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle,
+ String[] disallowedPackages) {
try {
- return mService.createProfileForUser(name, flags, userHandle);
+ return mService.createProfileForUser(name, flags, userHandle, disallowedPackages);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Similar to {@link #createProfileForUser(String, int, int, String[])}
+ * except bypassing the checking of {@link UserManager#DISALLOW_ADD_USER}.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @see #createProfileForUser(String, int, int, String[])
+ * @hide
+ */
+ public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
+ @UserIdInt int userHandle, String[] disallowedPackages) {
+ try {
+ return mService.createProfileForUserEvenWhenDisallowed(name, flags, userHandle,
+ disallowedPackages);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
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/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 7399b7b..e399be0 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -437,7 +437,7 @@
final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setData(uri);
+ intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
// note that docsui treats this as *force* show advanced. So sending
// false permits advanced to be shown based on user preferences.
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index f134943..3d094f7 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -586,8 +586,6 @@
*
* @param key The option key.
* @return Whether the option is present.
- *
- * @hide
*/
public boolean hasAdvancedOption(String key) {
return mAdvancedOptions != null && mAdvancedOptions.containsKey(key);
@@ -598,8 +596,6 @@
*
* @param key The option key.
* @return The option value.
- *
- * @hide
*/
public String getAdvancedStringOption(String key) {
if (mAdvancedOptions != null) {
@@ -613,8 +609,6 @@
*
* @param key The option key.
* @return The option value.
- *
- * @hide
*/
public int getAdvancedIntOption(String key) {
if (mAdvancedOptions != null) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 8f73518..ecf1770 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +31,7 @@
import android.os.RemoteException;
import android.print.PrintJobInfo;
import android.print.PrinterId;
+import android.print.PrinterInfo;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -247,6 +250,26 @@
public static final String EXTRA_PRINT_DOCUMENT_INFO =
"android.printservice.extra.PRINT_DOCUMENT_INFO";
+ /**
+ * When the {@link PendingIntent} declared via
+ * {@link PrinterInfo.Builder#setInfoIntent(PendingIntent)} is called this boolean extra
+ * will be filled in if the activity can select the printer.
+ *
+ * @see #EXTRA_SELECT_PRINTER
+ */
+ public static final String EXTRA_CAN_SELECT_PRINTER =
+ "android.printservice.extra.CAN_SELECT_PRINTER";
+
+ /**
+ * If this boolean extra is set to {@code true} in the {@link Activity#setResult(int, Intent)
+ * result data} from the activity specified in
+ * {@link PrinterInfo.Builder#setInfoIntent(PendingIntent)} the printer will be selected.
+ *
+ * @see #EXTRA_CAN_SELECT_PRINTER
+ */
+ public static final String EXTRA_SELECT_PRINTER =
+ "android.printservice.extra.SELECT_PRINTER";
+
private Handler mHandler;
private IPrintServiceClient mClient;
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index d409030..b31b295 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -438,21 +438,11 @@
public static final String PRIMARY_ORGANIZATION_ID = "primary_organization";
/**
- * Mark a person as having been contacted.
- *
- * @param resolver the ContentResolver to use
- * @param personId the person who was contacted
- * @deprecated see {@link android.provider.ContactsContract}
+ * This API is no longer supported as of O.
*/
@Deprecated
public static void markAsContacted(ContentResolver resolver, long personId) {
- Uri uri = ContentUris.withAppendedId(CONTENT_URI, personId);
- uri = Uri.withAppendedPath(uri, "update_contact_time");
- ContentValues values = new ContentValues();
- // There is a trigger in place that will update TIMES_CONTACTED when
- // LAST_TIME_CONTACTED is modified.
- values.put(LAST_TIME_CONTACTED, System.currentTimeMillis());
- resolver.update(uri, values, null, null);
+ // No longer supported.
}
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a1763c0..a07aee5 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -19,8 +19,6 @@
import android.accounts.Account;
import android.annotation.SystemApi;
import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
-import android.content.ActivityNotFoundException;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
@@ -44,7 +42,6 @@
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.View;
-import android.widget.Toast;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -118,6 +115,12 @@
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
/**
+ * Prefix for column names that are not visible to client apps.
+ * @hide
+ */
+ public static final String HIDDEN_COLUMN_PREFIX = "x_";
+
+ /**
* An optional URI parameter for insert, update, or delete queries
* that allows the caller
* to specify that it is a sync adapter. The default value is false. If true
@@ -661,6 +664,12 @@
ContentValues contentValues = new ContentValues();
resolver.update(Directory.CONTENT_URI, contentValues, null, null);
}
+
+ /**
+ * A query parameter that's passed to directory providers which indicates the client
+ * package name that has made the query requests.
+ */
+ public static final String CALLER_PACKAGE_PARAM_KEY = "callerPackage";
}
/**
@@ -863,6 +872,25 @@
*/
public static final String LAST_TIME_CONTACTED = "last_time_contacted";
+ /** @hide Raw value. */
+ public static final String RAW_TIMES_CONTACTED = HIDDEN_COLUMN_PREFIX + TIMES_CONTACTED;
+
+ /** @hide Raw value. */
+ public static final String RAW_LAST_TIME_CONTACTED =
+ HIDDEN_COLUMN_PREFIX + LAST_TIME_CONTACTED;
+
+ /**
+ * @hide
+ * Low res version. Same as {@link #TIMES_CONTACTED} but use it in CP2 for clarification.
+ */
+ public static final String LR_TIMES_CONTACTED = TIMES_CONTACTED;
+
+ /**
+ * @hide
+ * Low res version. Same as {@link #TIMES_CONTACTED} but use it in CP2 for clarification.
+ */
+ public static final String LR_LAST_TIME_CONTACTED = LAST_TIME_CONTACTED;
+
/**
* Is the contact starred?
* <P>Type: INTEGER (boolean)</P>
@@ -1669,7 +1697,7 @@
Uri uri = ContentUris.withAppendedId(CONTENT_URI, contactId);
ContentValues values = new ContentValues();
// TIMES_CONTACTED will be incremented when LAST_TIME_CONTACTED is modified.
- values.put(LAST_TIME_CONTACTED, System.currentTimeMillis());
+ values.put(LR_LAST_TIME_CONTACTED, System.currentTimeMillis());
resolver.update(uri, values, null, null);
}
@@ -4224,6 +4252,24 @@
/** The number of times the referenced {@link Data} has been used. */
public static final String TIMES_USED = "times_used";
+
+ /** @hide Raw value. */
+ public static final String RAW_LAST_TIME_USED = HIDDEN_COLUMN_PREFIX + LAST_TIME_USED;
+
+ /** @hide Raw value. */
+ public static final String RAW_TIMES_USED = HIDDEN_COLUMN_PREFIX + TIMES_USED;
+
+ /**
+ * @hide
+ * Low res version. Same as {@link #LAST_TIME_USED} but use it in CP2 for clarification.
+ */
+ public static final String LR_LAST_TIME_USED = LAST_TIME_USED;
+
+ /**
+ * @hide
+ * Low res version. Same as {@link #TIMES_USED} but use it in CP2 for clarification.
+ */
+ public static final String LR_TIMES_USED = TIMES_USED;
}
/**
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 6ddaf3b..1faada1 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,6 +19,10 @@
import static android.net.TrafficStats.KB_IN_BYTES;
import static android.system.OsConstants.SEEK_SET;
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
+import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
+
import android.annotation.Nullable;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@@ -36,8 +40,10 @@
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.storage.StorageVolume;
import android.system.ErrnoException;
@@ -53,6 +59,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
+import java.util.Objects;
/**
* Defines the contract between a documents provider and the platform.
@@ -370,19 +377,6 @@
public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
/**
- * Flag indicating that a document is an archive, and it's contents can be
- * obtained via {@link DocumentsProvider#queryChildDocuments}.
- * <p>
- * The <em>provider</em> support library offers utility classes to add common
- * archive support.
- *
- * @see #COLUMN_FLAGS
- * @see DocumentsProvider#queryChildDocuments(String, String[], String)
- * @hide
- */
- public static final int FLAG_ARCHIVE = 1 << 15;
-
- /**
* Flag indicating that a document is not complete, likely its
* contents are being downloaded. Partial files cannot be opened,
* copied, moved in the UI. But they can be deleted and retried
@@ -644,6 +638,8 @@
public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
/** {@hide} */
public static final String METHOD_EJECT_ROOT = "android:ejectRoot";
+ /** {@hide} */
+ public static final String METHOD_FIND_PATH = "android:findPath";
/** {@hide} */
public static final String EXTRA_PARENT_URI = "parentUri";
@@ -1307,6 +1303,59 @@
}
/**
+ * Finds the canonical path to the top of the tree. The return value starts
+ * from the top of the tree or the root document to the requested document,
+ * both inclusive.
+ *
+ * Document id should be unique across roots.
+ *
+ * @param treeUri treeUri of the document which path is requested.
+ * @return a list of documents ID starting from the top of the tree to the
+ * requested document, or {@code null} if failed.
+ * @see DocumentsProvider#findPath(String, String)
+ *
+ * {@hide}
+ */
+ public static List<String> findPath(ContentResolver resolver, Uri treeUri) {
+ checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
+
+ final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+ treeUri.getAuthority());
+ try {
+ return findPath(client, treeUri).getPath();
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to find path", e);
+ return null;
+ } finally {
+ ContentProviderClient.releaseQuietly(client);
+ }
+ }
+
+ /**
+ * Finds the canonical path. If uri is a document uri returns path to a root and
+ * its associated root id. If uri is a tree uri returns the path to the top of
+ * the tree. The {@link Path#getPath()} in the return value starts from the top of
+ * the tree or the root document to the requested document, both inclusive.
+ *
+ * Document id should be unique across roots.
+ *
+ * @param uri uri of the document which path is requested. It can be either a
+ * plain document uri or a tree uri.
+ * @return the path of the document.
+ * @see DocumentsProvider#findPath(String, String)
+ *
+ * {@hide}
+ */
+ public static Path findPath(ContentProviderClient client, Uri uri) throws RemoteException {
+ final Bundle in = new Bundle();
+ in.putParcelable(DocumentsContract.EXTRA_URI, uri);
+
+ final Bundle out = client.call(METHOD_FIND_PATH, null, in);
+
+ return out.getParcelable(DocumentsContract.EXTRA_RESULT);
+ }
+
+ /**
* Open the given image for thumbnail purposes, using any embedded EXIF
* thumbnail if available, and providing orientation hints from the parent
* image.
@@ -1345,4 +1394,102 @@
return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
}
+
+ /**
+ * Holds a path from a root to a particular document under it.
+ *
+ * @hide
+ */
+ public static final class Path implements Parcelable {
+
+ private final @Nullable String mRootId;
+ private final List<String> mPath;
+
+ /**
+ * Creates a Path.
+ *
+ * @param rootId the ID of the root. May be null.
+ * @param path the list of document ids from the parent document at
+ * position 0 to the child document.
+ */
+ public Path(String rootId, List<String> path) {
+ checkCollectionNotEmpty(path, "path");
+ checkCollectionElementsNotNull(path, "path");
+
+ mRootId = rootId;
+ mPath = path;
+ }
+
+ /**
+ * Returns the root id or null if the calling package doesn't have
+ * permission to access root information.
+ */
+ public @Nullable String getRootId() {
+ return mRootId;
+ }
+
+ /**
+ * Returns the path. The path is trimmed to the top of tree if
+ * calling package doesn't have permission to access those
+ * documents.
+ */
+ public List<String> getPath() {
+ return mPath;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof Path)) {
+ return false;
+ }
+ Path path = (Path) o;
+ return Objects.equals(mRootId, path.mRootId) &&
+ Objects.equals(mPath, path.mPath);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRootId, mPath);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("DocumentsContract.Path{")
+ .append("rootId=")
+ .append(mRootId)
+ .append(", path=")
+ .append(mPath)
+ .append("}")
+ .toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mRootId);
+ dest.writeStringList(mPath);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Path> CREATOR = new Creator<Path>() {
+ @Override
+ public Path createFromParcel(Parcel in) {
+ final String rootId = in.readString();
+ final List<String> path = in.createStringArrayList();
+ return new Path(rootId, path);
+ }
+
+ @Override
+ public Path[] newArray(int size) {
+ return new Path[size];
+ }
+ };
+ }
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 6117ce4..d75781b 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -20,6 +20,7 @@
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
+import static android.provider.DocumentsContract.METHOD_FIND_PATH;
import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
@@ -33,7 +34,9 @@
import static android.provider.DocumentsContract.getTreeDocumentId;
import static android.provider.DocumentsContract.isTreeUri;
+import android.Manifest;
import android.annotation.CallSuper;
+import android.annotation.Nullable;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -52,6 +55,7 @@
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Path;
import android.provider.DocumentsContract.Root;
import android.util.Log;
@@ -151,17 +155,7 @@
*/
@Override
public void attachInfo(Context context, ProviderInfo info) {
- mAuthority = info.authority;
-
- mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
- mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
- mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
- mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
- mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
- mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
- mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
- mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
+ registerAuthority(info.authority);
// Sanity check our setup
if (!info.exported) {
@@ -178,6 +172,28 @@
super.attachInfo(context, info);
}
+ /** {@hide} */
+ @Override
+ public void attachInfoForTesting(Context context, ProviderInfo info) {
+ registerAuthority(info.authority);
+
+ super.attachInfoForTesting(context, info);
+ }
+
+ private void registerAuthority(String authority) {
+ mAuthority = authority;
+
+ mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
+ mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
+ mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
+ mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
+ mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
+ mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+ mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
+ mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
+ }
+
/**
* Test if a document is descendant (child, grandchild, etc) from the given
* parent. For example, providers must implement this to support
@@ -323,6 +339,31 @@
}
/**
+ * Finds the canonical path for the requested document. The path must start
+ * from the parent document if parentDocumentId is not null or the root document
+ * if parentDocumentId is null. If there are more than one path to this document,
+ * return the most typical one. Include both the parent document or root document
+ * and the requested document in the returned path.
+ *
+ * <p>This API assumes that document ID has enough info to infer the root.
+ * Different roots should use different document ID to refer to the same
+ * document.
+ *
+ * @param childDocumentId the document which path is requested.
+ * @param parentDocumentId the document with which path starts if not null, or
+ * null to indicate path to root is requested.
+ * @return the path of the requested document. If parentDocumentId is null
+ * returned root ID must not be null. If parentDocumentId is not null
+ * returned root ID must be null.
+ *
+ * @hide
+ */
+ public Path findPath(String childDocumentId, @Nullable String parentDocumentId)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("findPath not supported.");
+ }
+
+ /**
* Return all roots currently provided. To display to users, you must define
* at least one root. You should avoid making network requests to keep this
* request fast.
@@ -873,6 +914,30 @@
// It's responsibility of the provider to revoke any grants, as the document may be
// still attached to another parents.
+ } else if (METHOD_FIND_PATH.equals(method)) {
+ final boolean isTreeUri = isTreeUri(documentUri);
+
+ if (isTreeUri) {
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+ } else {
+ getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
+ }
+
+ final String parentDocumentId = isTreeUri
+ ? DocumentsContract.getTreeDocumentId(documentUri)
+ : null;
+
+ final Path path = findPath(documentId, parentDocumentId);
+
+ // Ensure provider doesn't leak information to unprivileged callers.
+ if (isTreeUri
+ && (path.getRootId() != null
+ || !Objects.equals(path.getPath().get(0), parentDocumentId))) {
+ throw new IllegalStateException(
+ "Provider returns an invalid result for findPath.");
+ }
+
+ out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e62440a..d3a978c 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1853,7 +1853,6 @@
MOVED_TO_GLOBAL.add(Settings.Global.CALL_AUTO_RETRY);
MOVED_TO_GLOBAL.add(Settings.Global.DEBUG_APP);
MOVED_TO_GLOBAL.add(Settings.Global.WAIT_FOR_DEBUGGER);
- MOVED_TO_GLOBAL.add(Settings.Global.SHOW_PROCESSES);
MOVED_TO_GLOBAL.add(Settings.Global.ALWAYS_FINISH_ACTIVITIES);
MOVED_TO_GLOBAL.add(Settings.Global.TZINFO_UPDATE_CONTENT_URL);
MOVED_TO_GLOBAL.add(Settings.Global.TZINFO_UPDATE_METADATA_URL);
@@ -2791,7 +2790,8 @@
/**
* Control whether the process CPU usage meter should be shown.
*
- * @deprecated Use {@link Global#SHOW_PROCESSES} instead
+ * @deprecated This functionality is no longer available as of
+ * {@link android.os.Build.VERSION_CODES#N_MR1}.
*/
@Deprecated
public static final String SHOW_PROCESSES = Global.SHOW_PROCESSES;
@@ -6460,6 +6460,12 @@
public static final String WEB_ACTION_ENABLED = "web_action_enabled";
/**
+ * Has this pairable device been paired or upgraded from a previously paired system.
+ * @hide
+ */
+ public static final String DEVICE_PAIRED = "device_paired";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -6927,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
*/
@@ -7220,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} */
@@ -8056,23 +8075,82 @@
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";
/**
* The server used for captive portal detection upon a new conection. A
* 204 response code from the server is used for validation.
+ * TODO: remove this deprecated symbol.
*
* @hide
*/
public static final String CAPTIVE_PORTAL_SERVER = "captive_portal_server";
/**
+ * The URL used for HTTPS captive portal detection upon a new connection.
+ * A 204 response code from the server is used for validation.
+ *
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
+
+ /**
+ * The URL used for HTTP captive portal detection upon a new connection.
+ * A 204 response code from the server is used for validation.
+ *
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+
+ /**
+ * The URL used for fallback HTTP captive portal detection when previous HTTP
+ * and HTTPS captive portal detection attemps did not return a conclusive answer.
+ *
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
+
+ /**
* Whether to use HTTPS for network validation. This is enabled by default and the setting
* needs to be set to 0 to disable it. This setting is a misnomer because captive portals
* don't actually use HTTPS, but it's consistent with the other settings.
@@ -8082,6 +8160,14 @@
public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
/**
+ * Which User-Agent string to use in the header of the captive portal detection probes.
+ * The User-Agent field is unset when this setting has no value (HttpUrlConnection default).
+ *
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
+
+ /**
* Whether network service discovery is enabled.
*
* @hide
@@ -8480,7 +8566,11 @@
/**
* Control whether the process CPU usage meter should be shown.
+ *
+ * @deprecated This functionality is no longer available as of
+ * {@link android.os.Build.VERSION_CODES#N_MR1}.
*/
+ @Deprecated
public static final String SHOW_PROCESSES = "show_processes";
/**
@@ -8792,7 +8882,7 @@
public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
/**
- * WFC Mode.
+ * WFC mode on home/non-roaming network.
* <p>
* Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
*
@@ -8801,6 +8891,15 @@
public static final String WFC_IMS_MODE = "wfc_ims_mode";
/**
+ * WFC mode on roaming network.
+ * <p>
+ * Type: int - see {@link WFC_IMS_MODE} for values
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+ /**
* Whether WFC roaming is enabled
* <p>
* Type: int (0 for false, 1 for true)
@@ -8926,6 +9025,14 @@
public static final String DATABASE_DOWNGRADE_REASON = "database_downgrade_reason";
/**
+ * Flag to toggle journal mode WAL on or off for the contacts database. WAL is enabled by
+ * default. Set to 0 to disable.
+ *
+ * @hide
+ */
+ public static final String CONTACTS_DATABASE_WAL_ENABLED = "contacts_database_wal_enabled";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 8689dce..641e1ad 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -76,4 +76,5 @@
int onUserAdded(int userId, int parentId);
int onUserRemoved(int userId);
int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain);
+ int onDeviceOffBody();
}
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/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index b13e162..e6f58f5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -450,6 +450,28 @@
}
/**
+ * Inform the notification manager about snoozing a specific notification.
+ * <p>
+ * Use this if your listener has a user interface that allows the user to snooze a notification
+ * until a given time. It should be called after the user snoozes a single notification using
+ * your UI; upon being informed, the notification manager will actually remove the notification
+ * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
+ * snoozing period expires, you will get a
+ * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
+ * notification.
+ * @param key The key of the notification to snooze
+ * @param snoozeUntil A time in the future, in milliseconds.
+ */
+ public final void snoozeNotification(String key, long snoozeUntil) {
+ if (!isBound()) return;
+ try {
+ getNotificationInterface().snoozeNotificationFromListener(mWrapper, key, snoozeUntil);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ /**
* Inform the notification manager that these notifications have been viewed by the
* user. This should only be called when there is sufficient confidence that the user is
* looking at the notifications, such as when the notifications appear on the screen due to
diff --git a/core/java/android/service/notification/NotificationRankerService.java b/core/java/android/service/notification/NotificationRankerService.java
index 261d82d..928d5d8 100644
--- a/core/java/android/service/notification/NotificationRankerService.java
+++ b/core/java/android/service/notification/NotificationRankerService.java
@@ -102,6 +102,9 @@
/** Notification was canceled by the user banning the channel. */
public static final int REASON_CHANNEL_BANNED = 17;
+ /** Notification was snoozed. */
+ public static final int REASON_SNOOZED = 18;
+
private Handler mHandler;
/** @hide */
diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java
index 04730ec..c5339a4 100644
--- a/core/java/android/text/AutoText.java
+++ b/core/java/android/text/AutoText.java
@@ -18,11 +18,10 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.view.View;
import com.android.internal.util.XmlUtils;
-import android.view.View;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -97,10 +96,10 @@
sInstance = instance;
}
}
-
+
return instance;
}
-
+
/**
* Retrieves a possible spelling correction for the specified range
* of text. Returns null if no correction can be found.
@@ -119,7 +118,7 @@
*/
public static int getSize(View view) {
- return getInstance(view).getSize();
+ return getInstance(view).getSize();
}
/**
@@ -137,7 +136,7 @@
for (; here != TRIE_NULL; here = mTrie[here + TRIE_NEXT]) {
if (c == mTrie[here + TRIE_C]) {
- if ((i == end - 1)
+ if ((i == end - 1)
&& (mTrie[here + TRIE_OFF] != TRIE_NULL)) {
int off = mTrie[here + TRIE_OFF];
int len = mText.charAt(off);
@@ -174,7 +173,7 @@
while (true) {
XmlUtils.nextElement(parser);
- String element = parser.getName();
+ String element = parser.getName();
if (element == null || !(element.equals("word"))) {
break;
}
@@ -214,7 +213,7 @@
int herep = TRIE_ROOT;
// Keep track of the size of the dictionary
mSize++;
-
+
for (int i = 0; i < slen; i++) {
char c = src.charAt(i);
boolean found = false;
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index 59273f4..d84502f 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -16,11 +16,11 @@
package android.text;
+import static android.text.TextDirectionHeuristics.FIRSTSTRONG_LTR;
+
import android.annotation.Nullable;
import android.view.View;
-import static android.text.TextDirectionHeuristics.FIRSTSTRONG_LTR;
-
import java.util.Locale;
/**
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 8674c66..6edf845 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -16,31 +16,30 @@
package android.text;
+import android.graphics.BaseCanvas;
import android.graphics.Canvas;
import android.graphics.Paint;
/**
- * Please implement this interface if your CharSequence can do quick
- * draw/measure/widths calculations from an internal array.
- * {@hide}
+ * Please implement this interface if your CharSequence can do quick draw/measure/widths
+ * calculations from an internal array.
+ *
+ * @hide
*/
-public interface GraphicsOperations
-extends CharSequence
-{
+public interface GraphicsOperations extends CharSequence {
/**
* Just like {@link Canvas#drawText}.
*/
- void drawText(Canvas c, int start, int end,
- float x, float y, Paint p);
+ void drawText(BaseCanvas c, int start, int end,
+ float x, float y, Paint p);
/**
* Just like {@link Canvas#drawTextRun}.
- * {@hide}
*/
- void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
+ void drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd,
float x, float y, boolean isRtl, Paint p);
- /**
+ /**
* Just like {@link Paint#measureText}.
*/
float measureText(int start, int end, Paint p);
@@ -52,14 +51,12 @@
/**
* Just like {@link Paint#getTextRunAdvances}.
- * @hide
*/
float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
boolean isRtl, float[] advances, int advancesIndex, Paint paint);
/**
* Just like {@link Paint#getTextRunCursor}.
- * @hide
*/
int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
int cursorOpt, Paint p);
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 409994d..de8aa5a 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -16,19 +16,8 @@
package android.text;
-import com.android.internal.util.ArrayUtils;
-import org.ccil.cowan.tagsoup.HTMLSchema;
-import org.ccil.cowan.tagsoup.Parser;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-
import android.app.ActivityThread;
import android.app.Application;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Typeface;
@@ -47,11 +36,21 @@
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
-import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
+import com.android.internal.util.ArrayUtils;
+
+import org.ccil.cowan.tagsoup.HTMLSchema;
+import org.ccil.cowan.tagsoup.Parser;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index c596388..8967b70 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -16,8 +16,6 @@
package android.text;
-import android.text.TextUtils;
-
/**
* Bit definitions for an integer defining the basic content type of text
* held in an {@link Editable} object. Supported classes may be combined
@@ -51,19 +49,19 @@
* or flags.<p>
*/
public static final int TYPE_MASK_CLASS = 0x0000000f;
-
+
/**
* Mask of bits that determine the variation of
* the base content class.
*/
public static final int TYPE_MASK_VARIATION = 0x00000ff0;
-
+
/**
* Mask of bits that provide addition bit flags
* of options.
*/
public static final int TYPE_MASK_FLAGS = 0x00fff000;
-
+
/**
* Special content type for when no explicit type has been specified.
* This should be interpreted to mean that the target input connection
@@ -75,11 +73,11 @@
* flag is set.
*/
public static final int TYPE_NULL = 0x00000000;
-
+
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
-
+
/**
* Class for normal text. This class supports the following flags (only
* one of which should be set):
@@ -92,7 +90,7 @@
* variation, normal should be assumed.
*/
public static final int TYPE_CLASS_TEXT = 0x00000001;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize all characters. Overrides
* {@link #TYPE_TEXT_FLAG_CAP_WORDS} and
@@ -101,7 +99,7 @@
* this only affects languages where there are upper-case and lower-case letters.
*/
public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
* every word. Overrides {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This
@@ -110,7 +108,7 @@
* this only affects languages where there are upper-case and lower-case letters.
*/
public static final int TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
* each sentence. This value is explicitly defined
@@ -121,7 +119,7 @@
* this only affects languages where there are upper-case and lower-case letters.
*/
public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: the user is entering free-form
* text that should have auto-correction applied to it. Without this flag,
@@ -135,7 +133,7 @@
* the IME offers an interface to show suggestions.
*/
public static final int TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: the text editor (which means
* the application) is performing auto-completion of the text being entered
@@ -154,23 +152,23 @@
* it will rely on the Editor to pass completions/corrections.
*/
public static final int TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
- * entered into the field. If this flag is not set, the text field
+ * entered into the field. If this flag is not set, the text field
* will be constrained to a single line. The IME may also choose not to
* display an enter key when this flag is not set, as there should be no
* need to create new lines.
*/
public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: the regular text view associated
* with this should not be multi-line, but when a fullscreen input method
* is providing text it should use multiple lines if it can.
*/
public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000;
-
+
/**
* Flag for {@link #TYPE_CLASS_TEXT}: the input method does not need to
* display any dictionary-based candidates. This is useful for text views that
@@ -191,36 +189,36 @@
public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;
// ----------------------------------------------------------------------
-
+
/**
* Default variation of {@link #TYPE_CLASS_TEXT}: plain old normal text.
*/
public static final int TYPE_TEXT_VARIATION_NORMAL = 0x00000000;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a URI.
*/
public static final int TYPE_TEXT_VARIATION_URI = 0x00000010;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering an e-mail address.
*/
public static final int TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering the subject line of
* an e-mail.
*/
public static final int TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a short, possibly informal
* message such as an instant message or a text message.
*/
public static final int TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x00000040;
-
+
/**
- * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long, possibly
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long, possibly
* formal message such as the body of an e-mail.
*/
public static final int TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050;
@@ -229,34 +227,34 @@
* Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
*/
public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
*/
public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
*/
public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000080;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password, which should
* be visible to the user.
*/
public static final int TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
*/
public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering text to filter contents
* of a list etc.
*/
public static final int TYPE_TEXT_VARIATION_FILTER = 0x000000b0;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering text for phonetic
* pronunciation, such as a phonetic name field in contacts. This is mostly
@@ -264,7 +262,7 @@
* readings, like Japanese.
*/
public static final int TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0;
-
+
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering e-mail address inside
* of a web form. This was added in
@@ -290,7 +288,7 @@
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
-
+
/**
* Class for numeric text. This class supports the following flags:
* {@link #TYPE_NUMBER_FLAG_SIGNED} and
@@ -301,19 +299,19 @@
* the variation, normal should be assumed.</p>
*/
public static final int TYPE_CLASS_NUMBER = 0x00000002;
-
+
/**
* Flag of {@link #TYPE_CLASS_NUMBER}: the number is signed, allowing
* a positive or negative sign at the start.
*/
public static final int TYPE_NUMBER_FLAG_SIGNED = 0x00001000;
-
+
/**
* Flag of {@link #TYPE_CLASS_NUMBER}: the number is decimal, allowing
* a decimal point to provide fractional values.
*/
public static final int TYPE_NUMBER_FLAG_DECIMAL = 0x00002000;
-
+
// ----------------------------------------------------------------------
/**
@@ -340,17 +338,17 @@
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
-
+
/**
* Class for a phone number. This class currently supports no variations
* or flags.
*/
public static final int TYPE_CLASS_PHONE = 0x00000003;
-
+
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
-
+
/**
* Class for dates and times. It supports the
* following variations:
@@ -359,19 +357,19 @@
* {@link #TYPE_DATETIME_VARIATION_TIME}.
*/
public static final int TYPE_CLASS_DATETIME = 0x00000004;
-
+
/**
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
* both a date and time.
*/
public static final int TYPE_DATETIME_VARIATION_NORMAL = 0x00000000;
-
+
/**
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
* only a date.
*/
public static final int TYPE_DATETIME_VARIATION_DATE = 0x00000010;
-
+
/**
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
* only a time.
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index dc8e4b9..186d96b 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -17,7 +17,7 @@
package android.text;
import android.annotation.Nullable;
-import android.graphics.Canvas;
+import android.graphics.BaseCanvas;
import android.graphics.Paint;
import android.util.Log;
@@ -1357,7 +1357,8 @@
* Don't call this yourself -- exists for Canvas to use internally.
* {@hide}
*/
- public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
+ @Override
+ public void drawText(BaseCanvas c, int start, int end, float x, float y, Paint p) {
checkRange("drawText", start, end);
if (end <= mGapStart) {
@@ -1378,7 +1379,8 @@
* Don't call this yourself -- exists for Canvas to use internally.
* {@hide}
*/
- public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
+ @Override
+ public void drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd,
float x, float y, boolean isRtl, Paint p) {
checkRange("drawTextRun", start, end);
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 2a52961..03a2d62 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,11 +16,9 @@
package android.text;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
-import android.graphics.RectF;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 23b22d1..6262fc2 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -60,8 +60,6 @@
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
-import libcore.icu.ICU;
-
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.List;
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 3ed37b36..b5a8aca 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -23,15 +23,15 @@
import android.text.Spanned;
import android.text.SpannedString;
+import libcore.icu.ICU;
+import libcore.icu.LocaleData;
+
+import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
-import java.text.SimpleDateFormat;
-
-import libcore.icu.ICU;
-import libcore.icu.LocaleData;
/**
* Utility class for producing strings with formatted date/time.
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index cb8852c..f16e714 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -16,12 +16,16 @@
package android.text.format;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import com.android.internal.R;
+
+import libcore.icu.DateIntervalFormat;
+import libcore.icu.LocaleData;
+import libcore.icu.RelativeDateTimeFormatter;
+
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
@@ -30,10 +34,6 @@
import java.util.Locale;
import java.util.TimeZone;
-import libcore.icu.DateIntervalFormat;
-import libcore.icu.LocaleData;
-import libcore.icu.RelativeDateTimeFormatter;
-
/**
* This class contains various date-related utilities for creating text for things like
* elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc.
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index be52464..b67ac98 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -20,11 +20,10 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
+import android.net.NetworkUtils;
import android.text.BidiFormatter;
import android.text.TextUtils;
import android.view.View;
-import android.net.NetworkUtils;
-import android.net.TrafficStats;
import java.util.Locale;
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index 69c2cff..bbd9c9c 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -18,13 +18,13 @@
import android.util.TimeFormatException;
+import libcore.util.ZoneInfo;
+import libcore.util.ZoneInfoDB;
+
import java.io.IOException;
import java.util.Locale;
import java.util.TimeZone;
-import libcore.util.ZoneInfo;
-import libcore.util.ZoneInfoDB;
-
/**
* An alternative to the {@link java.util.Calendar} and
* {@link java.util.GregorianCalendar} classes. An instance of the Time class represents
diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java
index 3a63805..5a14092 100644
--- a/core/java/android/text/format/TimeFormatter.java
+++ b/core/java/android/text/format/TimeFormatter.java
@@ -22,12 +22,13 @@
import android.content.res.Resources;
+import libcore.icu.LocaleData;
+import libcore.util.ZoneInfo;
+
import java.nio.CharBuffer;
import java.util.Formatter;
import java.util.Locale;
import java.util.TimeZone;
-import libcore.icu.LocaleData;
-import libcore.util.ZoneInfo;
/**
* Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java.
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 14e0b4f..90559dc 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -19,19 +19,22 @@
import android.graphics.Paint;
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
-import android.view.KeyEvent;
-import android.view.View;
-import android.text.*;
+import android.text.Editable;
+import android.text.Emoji;
+import android.text.InputType;
+import android.text.Layout;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spanned;
import android.text.method.TextKeyListener.Capitalize;
import android.text.style.ReplacementSpan;
+import android.view.KeyEvent;
+import android.view.View;
import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
import java.text.BreakIterator;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
/**
* Abstract base class for key listeners.
diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java
index 880e46d..7d838e0 100644
--- a/core/java/android/text/method/CharacterPickerDialog.java
+++ b/core/java/android/text/method/CharacterPickerDialog.java
@@ -16,24 +16,25 @@
package android.text.method;
-import com.android.internal.R;
-
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
-import android.text.*;
+import android.text.Editable;
+import android.text.Selection;
import android.view.LayoutInflater;
-import android.view.View.OnClickListener;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
+import com.android.internal.R;
+
/**
* Dialog for choosing accented characters related to a base character.
*/
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
index e6f63d1..88ef388 100644
--- a/core/java/android/text/method/DateKeyListener.java
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -16,8 +16,8 @@
package android.text.method;
-import android.view.KeyEvent;
import android.text.InputType;
+import android.view.KeyEvent;
/**
* For entering dates in a text field.
@@ -32,7 +32,7 @@
return InputType.TYPE_CLASS_DATETIME
| InputType.TYPE_DATETIME_VARIATION_DATE;
}
-
+
@Override
protected char[] getAcceptedChars()
{
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
index bb8b0de..17abed6 100644
--- a/core/java/android/text/method/DialerKeyListener.java
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -16,10 +16,10 @@
package android.text.method;
-import android.view.KeyEvent;
-import android.view.KeyCharacterMap.KeyData;
import android.text.InputType;
import android.text.Spannable;
+import android.view.KeyCharacterMap.KeyData;
+import android.view.KeyEvent;
/**
* For dialing-only text entry
@@ -47,7 +47,7 @@
public int getInputType() {
return InputType.TYPE_CLASS_PHONE;
}
-
+
/**
* Overrides the superclass's lookup method to prefer the number field
* from the KeyEvent.
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index c95df46..4aeb39a 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -17,8 +17,8 @@
package android.text.method;
import android.text.InputType;
-import android.text.Spanned;
import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.view.KeyEvent;
@@ -133,7 +133,7 @@
}
return contentType;
}
-
+
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index e9db5fd..c3c7302 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -20,9 +20,9 @@
import android.text.NoCopySpan;
import android.text.Spannable;
import android.text.Spanned;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-import android.view.KeyCharacterMap;
/**
* This base class encapsulates the behavior for tracking the state of
@@ -86,7 +86,7 @@
* Value equals {@link KeyEvent#META_SYM_ON}.
*/
public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
-
+
/**
* Flag that indicates that the SHIFT key is locked in CAPS mode.
*/
@@ -111,11 +111,11 @@
private static final long META_CAP_USED = 1L << 32;
private static final long META_ALT_USED = 1L << 33;
private static final long META_SYM_USED = 1L << 34;
-
+
private static final long META_CAP_PRESSED = 1L << 40;
private static final long META_ALT_PRESSED = 1L << 41;
private static final long META_SYM_PRESSED = 1L << 42;
-
+
private static final long META_CAP_RELEASED = 1L << 48;
private static final long META_ALT_RELEASED = 1L << 49;
private static final long META_SYM_RELEASED = 1L << 50;
@@ -129,7 +129,7 @@
private static final long META_SYM_MASK = META_SYM_ON
| META_SYM_LOCKED | META_SYM_USED
| META_SYM_PRESSED | META_SYM_RELEASED;
-
+
private static final Object CAP = new NoCopySpan.Concrete();
private static final Object ALT = new NoCopySpan.Concrete();
private static final Object SYM = new NoCopySpan.Concrete();
@@ -150,7 +150,7 @@
/**
* Gets the state of the meta keys.
- *
+ *
* @param text the buffer in which the meta key would have been pressed.
*
* @return an integer in which each bit set to one represents a pressed
@@ -448,7 +448,7 @@
/**
* Gets the state of the meta keys.
- *
+ *
* @param state the current meta state bits.
*
* @return an integer in which each bit set to one represents a pressed
@@ -635,26 +635,26 @@
/**
* The meta key has been pressed but has not yet been used.
*/
- private static final int PRESSED =
+ private static final int PRESSED =
Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
/**
* The meta key has been pressed and released but has still
* not yet been used.
*/
- private static final int RELEASED =
+ private static final int RELEASED =
Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
/**
* The meta key has been pressed and used but has not yet been released.
*/
- private static final int USED =
+ private static final int USED =
Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
/**
* The meta key has been pressed and released without use, and then
* pressed again; it may also have been released again.
*/
- private static final int LOCKED =
+ private static final int LOCKED =
Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
}
diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java
index 01979fd..f6fe575 100644
--- a/core/java/android/text/method/MovementMethod.java
+++ b/core/java/android/text/method/MovementMethod.java
@@ -16,10 +16,10 @@
package android.text.method;
-import android.widget.TextView;
+import android.text.Spannable;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.text.*;
+import android.widget.TextView;
/**
* Provides cursor positioning, scrolling and text selection functionality in a {@link TextView}.
diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java
index 95ac0a1..5770482 100644
--- a/core/java/android/text/method/MultiTapKeyListener.java
+++ b/core/java/android/text/method/MultiTapKeyListener.java
@@ -16,13 +16,16 @@
package android.text.method;
-import android.view.KeyEvent;
-import android.view.View;
import android.os.Handler;
import android.os.SystemClock;
-import android.text.*;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.SpanWatcher;
+import android.text.Spannable;
import android.text.method.TextKeyListener.Capitalize;
import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.View;
/**
* This is the standard key listener for alphabetic input on 12-key
@@ -81,7 +84,7 @@
public int getInputType() {
return makeTextContentType(mCapitalize, mAutoText);
}
-
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
int selStart, selEnd;
@@ -198,7 +201,7 @@
if (selEnd != oldStart) {
Selection.setSelection(content, oldStart, selEnd);
- content.setSpan(TextKeyListener.LAST_TYPED,
+ content.setSpan(TextKeyListener.LAST_TYPED,
oldStart, selEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index 988d566..6b12b7e 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -16,14 +16,14 @@
package android.text.method;
-import android.view.KeyEvent;
-import android.view.View;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
+import android.view.KeyEvent;
+import android.view.View;
/**
* For numeric text entry
@@ -91,7 +91,7 @@
return false;
}
-
+
@Override
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
index 88a69b9..4485e38 100644
--- a/core/java/android/text/method/PasswordTransformationMethod.java
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -16,18 +16,18 @@
package android.text.method;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
-import android.graphics.Rect;
-import android.view.View;
import android.text.Editable;
import android.text.GetChars;
import android.text.NoCopySpan;
+import android.text.Spannable;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.Spanned;
-import android.text.Spannable;
import android.text.style.UpdateLayout;
+import android.view.View;
import java.lang.ref.WeakReference;
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index b17f502..bea68b1 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -16,7 +16,12 @@
package android.text.method;
-import android.text.*;
+import android.text.AutoText;
+import android.text.Editable;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
import android.text.method.TextKeyListener.Capitalize;
import android.util.SparseArray;
import android.view.KeyCharacterMap;
@@ -80,7 +85,7 @@
public int getInputType() {
return makeTextContentType(mAutoCap, mAutoText);
}
-
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
int selStart, selEnd;
@@ -191,9 +196,9 @@
}
}
- if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
- Character.isLowerCase(i) &&
- TextKeyListener.shouldCap(mAutoCap, content, selStart)) {
+ if ((pref & TextKeyListener.AUTO_CAP) != 0
+ && Character.isLowerCase(i)
+ && TextKeyListener.shouldCap(mAutoCap, content, selStart)) {
int where = content.getSpanEnd(TextKeyListener.CAPPED);
int flags = content.getSpanFlags(TextKeyListener.CAPPED);
@@ -361,9 +366,9 @@
View view) {
int len = end - start;
boolean changecase = false;
-
+
String replacement = AutoText.get(src, start, end, view);
-
+
if (replacement == null) {
String key = TextUtils.substring(src, start, end).toLowerCase();
replacement = AutoText.get(key, 0, end - start, view);
@@ -372,7 +377,7 @@
if (replacement == null)
return null;
}
-
+
int caps = 0;
if (changecase) {
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
index b9f5d5f4..4f422cb 100644
--- a/core/java/android/text/method/ScrollingMovementMethod.java
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -16,10 +16,11 @@
package android.text.method;
+import android.text.Layout;
+import android.text.Spannable;
import android.view.MotionEvent;
-import android.text.*;
-import android.widget.TextView;
import android.view.View;
+import android.widget.TextView;
/**
* A movement method that interprets movement keys by scrolling the text buffer.
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 994f3d7..9cbda9c 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -22,11 +22,16 @@
import android.os.Handler;
import android.provider.Settings;
import android.provider.Settings.System;
-import android.text.*;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.SpanWatcher;
+import android.text.Spannable;
+import android.text.TextUtils;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-import android.text.InputType;
import java.lang.ref.WeakReference;
@@ -127,7 +132,7 @@
public int getInputType() {
return makeTextContentType(mAutoCap, mAutoText);
}
-
+
@Override
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
@@ -213,7 +218,7 @@
public int getInputType() {
return InputType.TYPE_NULL;
}
-
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
return false;
@@ -230,7 +235,7 @@
public void clearMetaKeyState(View view, Editable content, int states) {
}
-
+
public static NullKeyListener getInstance() {
if (sInstance != null)
return sInstance;
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
index c5bfd5c..01f4086 100644
--- a/core/java/android/text/method/TimeKeyListener.java
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -16,8 +16,8 @@
package android.text.method;
-import android.view.KeyEvent;
import android.text.InputType;
+import android.view.KeyEvent;
/**
* For entering times in a text field.
@@ -32,7 +32,7 @@
return InputType.TYPE_CLASS_DATETIME
| InputType.TYPE_DATETIME_VARIATION_TIME;
}
-
+
@Override
protected char[] getAcceptedChars()
{
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index 89ed08c..17938a8 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.icu.text.BreakIterator;
-import android.text.Selection;
import android.text.CharSequenceCharacterIterator;
+import android.text.Selection;
import java.util.Locale;
diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java
index 20b6886..3524179 100644
--- a/core/java/android/text/style/DrawableMarginSpan.java
+++ b/core/java/android/text/style/DrawableMarginSpan.java
@@ -16,11 +16,11 @@
package android.text.style;
-import android.graphics.drawable.Drawable;
-import android.graphics.Paint;
import android.graphics.Canvas;
-import android.text.Spanned;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
import android.text.Layout;
+import android.text.Spanned;
public class DrawableMarginSpan
implements LeadingMarginSpan, LineHeightSpan
diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java
index cf9a705..304c83f 100644
--- a/core/java/android/text/style/IconMarginSpan.java
+++ b/core/java/android/text/style/IconMarginSpan.java
@@ -16,11 +16,11 @@
package android.text.style;
-import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.text.Spanned;
+import android.graphics.Paint;
import android.text.Layout;
+import android.text.Spanned;
public class IconMarginSpan
implements LeadingMarginSpan, LineHeightSpan
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 339d885..5bd2d60 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -16,8 +16,8 @@
package android.text.style;
-import android.graphics.Paint;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.os.Parcel;
import android.text.Layout;
import android.text.ParcelableSpan;
@@ -39,7 +39,7 @@
* Returns the amount by which to adjust the leading margin. Positive values
* move away from the leading edge of the paragraph, negative values move
* towards it.
- *
+ *
* @param first true if the request is for the first line of a paragraph,
* false for subsequent lines
* @return the offset for the margin.
@@ -49,7 +49,7 @@
/**
* Renders the leading margin. This is called before the margin has been
* adjusted by the value returned by {@link #getLeadingMargin(boolean)}.
- *
+ *
* @param c the canvas
* @param p the paint. The this should be left unchanged on exit.
* @param x the current position of the margin
@@ -98,11 +98,11 @@
*/
public static class Standard implements LeadingMarginSpan, ParcelableSpan {
private final int mFirst, mRest;
-
+
/**
* Constructor taking separate indents for the first and subsequent
* lines.
- *
+ *
* @param first the indent for the first line of the paragraph
* @param rest the indent for the remaining lines of the paragraph
*/
@@ -123,7 +123,7 @@
mFirst = src.readInt();
mRest = src.readInt();
}
-
+
public int getSpanTypeId() {
return getSpanTypeIdInternal();
}
@@ -132,7 +132,7 @@
public int getSpanTypeIdInternal() {
return TextUtils.LEADING_MARGIN_SPAN;
}
-
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java
index 854aeaf..9c7859f 100644
--- a/core/java/android/text/style/LineBackgroundSpan.java
+++ b/core/java/android/text/style/LineBackgroundSpan.java
@@ -16,8 +16,8 @@
package android.text.style;
-import android.graphics.Paint;
import android.graphics.Canvas;
+import android.graphics.Paint;
public interface LineBackgroundSpan
extends ParagraphStyle
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
index b842851..479ff0e 100644
--- a/core/java/android/text/style/LocaleSpan.java
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -16,8 +16,6 @@
package android.text.style;
-import com.android.internal.util.Preconditions;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
@@ -27,6 +25,8 @@
import android.text.TextPaint;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
import java.util.Locale;
/**
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
index 0b0a82c..7217e1e 100644
--- a/core/java/android/text/style/QuoteSpan.java
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -17,8 +17,8 @@
package android.text.style;
import android.annotation.ColorInt;
-import android.graphics.Paint;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.os.Parcel;
import android.text.Layout;
import android.text.ParcelableSpan;
@@ -34,7 +34,7 @@
super();
mColor = 0xff0000ff;
}
-
+
public QuoteSpan(@ColorInt int color) {
super();
mColor = color;
@@ -43,7 +43,7 @@
public QuoteSpan(Parcel src) {
mColor = src.readInt();
}
-
+
public int getSpanTypeId() {
return getSpanTypeIdInternal();
}
@@ -52,7 +52,7 @@
public int getSpanTypeIdInternal() {
return TextUtils.QUOTE_SPAN;
}
-
+
public int describeContents() {
return 0;
}
@@ -70,7 +70,7 @@
public int getColor() {
return mColor;
}
-
+
public int getLeadingMargin(boolean first) {
return STRIPE_WIDTH + GAP_WIDTH;
}
diff --git a/core/java/android/text/style/ReplacementSpan.java b/core/java/android/text/style/ReplacementSpan.java
index 07190b2..5f94ad0 100644
--- a/core/java/android/text/style/ReplacementSpan.java
+++ b/core/java/android/text/style/ReplacementSpan.java
@@ -19,8 +19,8 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.graphics.Paint;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.text.TextPaint;
public abstract class ReplacementSpan extends MetricAffectingSpan {
diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java
index e9153dd..c5e5df0 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -16,14 +16,14 @@
package android.text.style;
-import java.text.NumberFormat;
-import java.util.Locale;
-
import android.os.Parcel;
import android.os.PersistableBundle;
import android.text.ParcelableSpan;
import android.text.TextUtils;
+import java.text.NumberFormat;
+import java.util.Locale;
+
/**
* A span that supplies additional meta-data for the associated text intended
* for text-to-speech engines. If the text is being processed by a
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index ca037a2..7e6eb49 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -20,16 +20,21 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.telephony.PhoneNumberUtils;
-import android.text.method.LinkMovementMethod;
-import android.text.method.MovementMethod;
-import android.text.style.URLSpan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.method.MovementMethod;
+import android.text.style.URLSpan;
import android.util.Patterns;
import android.webkit.WebView;
import android.widget.TextView;
+import com.android.i18n.phonenumbers.PhoneNumberMatch;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
+
+import libcore.util.EmptyArray;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Retention;
@@ -42,12 +47,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import com.android.i18n.phonenumbers.PhoneNumberMatch;
-import com.android.i18n.phonenumbers.PhoneNumberUtil;
-import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
-
-import libcore.util.EmptyArray;
-
/**
* Linkify take a piece of text and a regular expression and turns all of the
* regex matches in the text into clickable links. This is particularly
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index a95da97..b6d8aa4 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -16,19 +16,18 @@
package android.transition;
-import android.animation.AnimatorSet;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.PointF;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.RectEvaluator;
+import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Path;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
diff --git a/core/java/android/transition/ChangeScroll.java b/core/java/android/transition/ChangeScroll.java
index e092685..8a3fd1c 100644
--- a/core/java/android/transition/ChangeScroll.java
+++ b/core/java/android/transition/ChangeScroll.java
@@ -19,8 +19,6 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.transition.Transition;
-import android.transition.TransitionValues;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java
index 9749121..4b0b065 100644
--- a/core/java/android/transition/ChangeTransform.java
+++ b/core/java/android/transition/ChangeTransform.java
@@ -30,6 +30,7 @@
import android.view.GhostView;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.internal.R;
/**
diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java
index 3445ef2..5f078ca 100644
--- a/core/java/android/transition/Explode.java
+++ b/core/java/android/transition/Explode.java
@@ -15,8 +15,6 @@
*/
package android.transition;
-import com.android.internal.R;
-
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.content.Context;
@@ -26,6 +24,8 @@
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
+
+import com.android.internal.R;
/**
* This transition tracks changes to the visibility of target views in the
* start and end scenes and moves views in or out from the edges of the
diff --git a/core/java/android/transition/PatternPathMotion.java b/core/java/android/transition/PatternPathMotion.java
index f23863f..7a2c191 100644
--- a/core/java/android/transition/PatternPathMotion.java
+++ b/core/java/android/transition/PatternPathMotion.java
@@ -15,8 +15,6 @@
*/
package android.transition;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
@@ -25,6 +23,8 @@
import android.util.AttributeSet;
import android.util.PathParser;
+import com.android.internal.R;
+
/**
* A PathMotion that takes a Path pattern and applies it to the separation between two points.
* The starting point of the Path will be moved to the origin and the end point will be scaled
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index 2645f86..9cf3210 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -26,6 +26,7 @@
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
+
import com.android.internal.R;
import java.lang.annotation.Retention;
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index cbf76bc..4c5a717 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -17,11 +17,6 @@
package android.transition;
import android.annotation.TransitionRes;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -32,6 +27,11 @@
import android.view.InflateException;
import android.view.ViewGroup;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
diff --git a/core/java/android/transition/TranslationAnimationCreator.java b/core/java/android/transition/TranslationAnimationCreator.java
index b07f3f8..ae76e62 100644
--- a/core/java/android/transition/TranslationAnimationCreator.java
+++ b/core/java/android/transition/TranslationAnimationCreator.java
@@ -15,8 +15,6 @@
*/
package android.transition;
-import com.android.internal.R;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -25,6 +23,8 @@
import android.transition.Transition.TransitionListener;
import android.view.View;
+import com.android.internal.R;
+
/**
* This class is used by Slide and Explode to create an animator that goes from the start
* position to the end position. It takes into account the canceled position so that it
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index f4db4d6..b7099b6 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -268,6 +268,10 @@
}
public void setTo(DisplayMetrics o) {
+ if (this == o) {
+ return;
+ }
+
widthPixels = o.widthPixels;
heightPixels = o.heightPixels;
density = o.density;
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
new file mode 100644
index 0000000..f4eb132
--- /dev/null
+++ b/core/java/android/util/Half.java
@@ -0,0 +1,367 @@
+/*
+ * 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.util;
+
+/**
+ * <p>Half is a utility class to manipulate half-precision 16-bit
+ * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
+ * floating point data types (also called fp16 or binary16). A half-precision
+ * float is stored in a short data type. A half-precision float can be
+ * created from or converted to single-precision floats.</p>
+ *
+ * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p>
+ * <ul>
+ * <li>Sign bit: 1 bit</li>
+ * <li>Exponent width: 5 bits</li>
+ * <li>Mantissa: 10 bits</li>
+ * </ul>
+ *
+ * <p>The format is laid out thusly:</p>
+ * <pre>
+ * 1 11111 1111111111
+ * ^ --^-- -----^----
+ * sign | |_______ mantissa
+ * |
+ * -- exponent
+ * </pre>
+ *
+ * @hide
+ */
+public final class Half {
+ /**
+ * The number of bits used to represent a half-precision float value.
+ */
+ public static final int SIZE = 16;
+
+ /**
+ * Epsilon is the difference between 1.0 and the next value representable
+ * by a half-precision floating-point.
+ */
+ public static final short EPSILON = (short) 0x1400;
+ /**
+ * Smallest negative value a half-precision float may have.
+ */
+ public static final short LOWEST_VALUE = (short) 0xfbff;
+ /**
+ * Maximum exponent a finite half-precision float may have.
+ */
+ public static final short MAX_EXPONENT = 15;
+ /**
+ * Maximum positive finite value a half-precision float may have.
+ */
+ public static final short MAX_VALUE = (short) 0x7bff;
+ /**
+ * Minimum exponent a normalized half-precision float may have.
+ */
+ public static final short MIN_EXPONENT = -14;
+ /**
+ * Smallest positive normal value a half-precision float may have.
+ */
+ public static final short MIN_NORMAL = (short) 0x0400;
+ /**
+ * Smallest positive non-zero value a half-precision float may have.
+ */
+ public static final short MIN_VALUE = (short) 0x0001;
+ /**
+ * A Not-a-Number representation of a half-precision float.
+ */
+ public static final short NaN = (short) 0x7e00;
+ /**
+ * Negative infinity of type half-precision float.
+ */
+ public static final short NEGATIVE_INFINITY = (short) 0xfc00;
+ /**
+ * Negative 0 of type half-precision float.
+ */
+ public static final short NEGATIVE_ZERO = (short) 0x8000;
+ /**
+ * Positive infinity of type half-precision float.
+ */
+ public static final short POSITIVE_INFINITY = (short) 0x7c00;
+ /**
+ * Positive 0 of type half-precision float.
+ */
+ public static final short POSITIVE_ZERO = (short) 0x0000;
+
+ private static final int FP16_SIGN_SHIFT = 15;
+ private static final int FP16_EXPONENT_SHIFT = 10;
+ private static final int FP16_EXPONENT_MASK = 0x1f;
+ private static final int FP16_MANTISSA_MASK = 0x3ff;
+ private static final int FP16_EXPONENT_BIAS = 15;
+
+ private static final int FP32_SIGN_SHIFT = 31;
+ private static final int FP32_EXPONENT_SHIFT = 23;
+ private static final int FP32_EXPONENT_MASK = 0xff;
+ private static final int FP32_MANTISSA_MASK = 0x7fffff;
+ private static final int FP32_EXPONENT_BIAS = 127;
+
+ private static final int FP32_DENORMAL_MAGIC = 126 << 23;
+ private static final float FP32_DENORMAL_FLOAT =
+ Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
+
+ private Half() {
+ }
+
+ /**
+ * Returns the sign of the specified half-precision float.
+ *
+ * @param h A half-precision float value
+ * @return 1 if the value is positive, -1 if the value is negative
+ */
+ public static int getSign(short h) {
+ return (h >>> FP16_SIGN_SHIFT) == 0 ? 1 : -1;
+ }
+
+ /**
+ * Returns the unbiased exponent used in the representation of
+ * the specified half-precision float value. if the value is NaN
+ * or infinite, this* method returns {@link #MAX_EXPONENT} + 1.
+ * If the argument is* 0 or denormal, this method returns
+ * {@link #MIN_EXPONENT} - 1.
+ *
+ * @param h A half-precision float value
+ * @return The unbiased exponent of the specified value
+ */
+ public static int getExponent(short h) {
+ return ((h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK) - FP16_EXPONENT_BIAS;
+ }
+
+ /**
+ * Returns the mantissa, or significand, used in the representation
+ * of the specified half-precision float value.
+ *
+ * @param h A half-precision float value
+ * @return The mantissa, or significand, of the specified vlaue
+ */
+ public static int getMantissa(short h) {
+ return h & FP16_MANTISSA_MASK;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value represents
+ * infinity, false otherwise.
+ *
+ * @param h A half-precision float value
+ * @return true if the value is positive infinity or negative infinity,
+ * false otherwise
+ */
+ public static boolean isInfinite(short h) {
+ int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (h ) & FP16_MANTISSA_MASK;
+ return e == 0x1f && m == 0;
+ }
+
+ /**
+ * Returns true if the specified half-precision float value represents
+ * a Not-a-Number, false otherwise.
+ *
+ * @param h A half-precision float value
+ * @return true if the value is a NaN, false otherwise
+ */
+ public static boolean isNaN(short h) {
+ int e = (h >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (h ) & FP16_MANTISSA_MASK;
+ return e == 0x1f && m != 0;
+ }
+
+ /**
+ * <p>Converts the specified half-precision float value into a
+ * single-precision float value with the following special cases:</p>
+ * <ul>
+ * <li>If the input is {@link #NaN}, the returned* value is {@link Float#NaN}</li>
+ * <li>If the input is {@link #POSITIVE_INFINITY} or
+ * {@link #NEGATIVE_INFINITY}, the returned value is respectively
+ * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
+ * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li>
+ * <li>Otherwise, the returned value is a normalized single-precision float value</li>
+ * </ul>
+ *
+ * @param h The half-precision float value to convert to single-precision
+ * @return A normalized single-precision float value
+ */
+ public static float toFloat(short h) {
+ int bits = h & 0xffff;
+ int s = (bits >>> FP16_SIGN_SHIFT );
+ int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (bits ) & FP16_MANTISSA_MASK;
+
+ int outE = 0;
+ int outM = 0;
+
+ if (e == 0) { // Denormal or 0
+ if (m != 0) {
+ // Convert denorm fp16 into normalized fp32
+ float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m);
+ o -= FP32_DENORMAL_FLOAT;
+ return s == 0 ? o : -o;
+ }
+ } else {
+ outM = m << 13;
+ if (e == 0x1f) { // Infinite or NaN
+ outE = 0xff;
+ } else {
+ outE = e - FP16_EXPONENT_BIAS + FP32_EXPONENT_BIAS;
+ }
+ }
+
+ int out = (s << FP32_SIGN_SHIFT) | (outE << FP32_EXPONENT_SHIFT) | outM;
+ return Float.intBitsToFloat(out);
+ }
+
+ /**
+ * <p>Converts the specified single-precision float value into a
+ * half-precision float value with the following special cases:</p>
+ * <ul>
+ * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
+ * value is {@link #NaN}</li>
+ * <li>If the input is {@link Float#POSITIVE_INFINITY} or
+ * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively
+ * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li>
+ * <li>If the input is 0 (positive or negative), the returned value is
+ * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+ * <li>If the input is a less than {@link #MIN_VALUE}, the returned value
+ * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+ * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value
+ * is a denorm half-precision float</li>
+ * <li>Otherwise, the returned value is rounded to the nearest
+ * representable half-precision float value</li>
+ * </ul>
+ *
+ * @param f The single-precision float value to convert to half-precision
+ * @return A half-precision float value
+ */
+ @SuppressWarnings("StatementWithEmptyBody")
+ public static short valueOf(float f) {
+ int bits = Float.floatToRawIntBits(f);
+ int s = (bits >>> FP32_SIGN_SHIFT );
+ int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_EXPONENT_MASK;
+ int m = (bits ) & FP32_MANTISSA_MASK;
+
+ int outE = 0;
+ int outM = 0;
+
+ if (e == 0xff) { // Infinite or NaN
+ outE = 0x1f;
+ outM = m != 0 ? 0x200 : 0;
+ } else {
+ e = e - FP32_EXPONENT_BIAS + FP16_EXPONENT_BIAS;
+ if (e >= 0x1f) { // Overflow
+ outE = 0x31;
+ } else if (e <= 0) { // Underflow
+ if (e < -10) {
+ // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
+ } else {
+ // The fp32 value is a normalized float less than MIN_NORMAL,
+ // we convert to a denorm fp16
+ m = (m | 0x800000) >> (1 - e);
+ if ((m & 0x1000) != 0) m += 0x2000;
+ outM = m >> 13;
+ }
+ } else {
+ outE = e;
+ outM = m >> 13;
+ if ((m & 0x1000) != 0) {
+ // Round to nearest "0.5" up
+ int out = (outE << FP16_EXPONENT_SHIFT) | outM;
+ out++;
+ out |= (s << FP16_SIGN_SHIFT);
+ return (short) out;
+ }
+ }
+ }
+
+ int out = (s << FP16_SIGN_SHIFT) | (outE << FP16_EXPONENT_SHIFT) | outM;
+ return (short) out;
+ }
+
+ /**
+ * Returns a string representation of the specified half-precision
+ * float value. Calling this method is equivalent to calling
+ * <code>Float.toString(toFloat(h))</code>. See {@link Float#toString(float)}
+ * for more information on the format of the string representation.
+ *
+ * @param h A half-precision float value
+ * @return A string representation of the specified value
+ */
+ public static String toString(short h) {
+ return Float.toString(toFloat(h));
+ }
+
+ /**
+ * <p>Returns a hexadecimal string representation of the specified half-precision
+ * float value. If the value is a NaN, the result is <code>"NaN"</code>,
+ * otherwise the result follows this format:</p>
+ * <ul>
+ * <li>If the sign is positive, no sign character appears in the result</li>
+ * <li>If the sign is negative, the first character is <code>'-'</code></li>
+ * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
+ * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
+ * <li>If the value has a normalized representation, the exponent and
+ * mantissa are represented in the string in two fields. The mantissa starts
+ * with <code>"0x1."</code> followed by its lowercase hexadecimal
+ * representation. Trailing zeroes are removed unless all digits are 0, then
+ * a single zero is used. The mantissa representation is followed by the
+ * exponent, represented by <code>"p"</code>, itself followed by a decimal
+ * string of the unbiased exponent</li>
+ * <li>If the value has a denormal representation, the mantissa starts
+ * with <code>"0x0."</code> followed by its lowercase hexadecimal
+ * representation. Trailing zeroes are removed unless all digits are 0, then
+ * a single zero is used. The mantissa representation is followed by the
+ * exponent, represented by <code>"p-14"</code></li>
+ * </ul>
+ *
+ * @param h A half-precision float value
+ * @return A hexadecimal string representation of the specified value
+ */
+ public static String toHexString(short h) {
+ StringBuilder o = new StringBuilder();
+
+ int bits = h & 0xffff;
+ int s = (bits >>> FP16_SIGN_SHIFT );
+ int e = (bits >>> FP16_EXPONENT_SHIFT) & FP16_EXPONENT_MASK;
+ int m = (bits ) & FP16_MANTISSA_MASK;
+
+ if (e == 0x1f) { // Infinite or NaN
+ if (m == 0) {
+ if (s == 1) o.append('-');
+ o.append("Infinity");
+ } else {
+ o.append("NaN");
+ }
+ } else {
+ if (s == 1) o.append('-');
+ if (e == 0) {
+ if (m == 0) {
+ o.append("0x0.0p0");
+ } else {
+ o.append("0x0.");
+ String mantissa = Integer.toHexString(m);
+ o.append(mantissa.replaceFirst("0{2,}$", ""));
+ o.append("p-14");
+ }
+ } else {
+ o.append("0x1.");
+ String mantissa = Integer.toHexString(m);
+ o.append(mantissa.replaceFirst("0{2,}$", ""));
+ o.append('p');
+ o.append(Integer.toString(e - FP16_EXPONENT_BIAS));
+ }
+ }
+
+ return o.toString();
+ }
+}
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 39f66a5..665c583 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -20,44 +20,49 @@
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.Deque;
+import java.util.ArrayDeque;
/**
* @hide
*/
public final class LocalLog {
- private LinkedList<String> mLog;
- private int mMaxLines;
- private long mNow;
+ private final Deque<String> mLog;
+ private final int mMaxLines;
public LocalLog(int maxLines) {
- mLog = new LinkedList<String>();
- mMaxLines = maxLines;
+ mMaxLines = Math.max(0, maxLines);
+ mLog = new ArrayDeque<>(mMaxLines);
}
- public synchronized void log(String msg) {
- if (mMaxLines > 0) {
- mNow = System.currentTimeMillis();
- StringBuilder sb = new StringBuilder();
- Calendar c = Calendar.getInstance();
- c.setTimeInMillis(mNow);
- sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
- mLog.add(sb.toString() + " - " + msg);
- while (mLog.size() > mMaxLines) mLog.remove();
+ public void log(String msg) {
+ if (mMaxLines <= 0) {
+ return;
}
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(System.currentTimeMillis());
+ append(String.format("%tm-%td %tH:%tM:%tS.%tL - %s", c, c, c, c, c, c, msg));
+ }
+
+ private synchronized void append(String logLine) {
+ while (mLog.size() >= mMaxLines) {
+ mLog.remove();
+ }
+ mLog.add(logLine);
}
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- Iterator<String> itr = mLog.listIterator(0);
+ Iterator<String> itr = mLog.iterator();
while (itr.hasNext()) {
pw.println(itr.next());
}
}
public synchronized void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
- for (int i = mLog.size() - 1; i >= 0; i--) {
- pw.println(mLog.get(i));
+ Iterator<String> itr = mLog.descendingIterator();
+ while (itr.hasNext()) {
+ pw.println(itr.next());
}
}
@@ -69,6 +74,9 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mLog.dump(fd, pw, args);
}
+ public void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.reverseDump(fd, pw, args);
+ }
}
public ReadOnlyLocalLog readOnlyLocalLog() {
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index f1c8c7d..6f314d0 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -16,6 +16,8 @@
import android.graphics.Path;
+import dalvik.annotation.optimization.FastNative;
+
/**
* @hide
*/
@@ -119,14 +121,24 @@
// Native functions are defined below.
private static native void nParseStringForPath(long pathPtr, String pathString,
int stringLength);
- private static native void nCreatePathFromPathData(long outPathPtr, long pathData);
- private static native long nCreateEmptyPathData();
- private static native long nCreatePathData(long nativePtr);
private static native long nCreatePathDataFromString(String pathString, int stringLength);
+
+ // ----------------- @FastNative -----------------------
+
+ @FastNative
+ private static native void nCreatePathFromPathData(long outPathPtr, long pathData);
+ @FastNative
+ private static native long nCreateEmptyPathData();
+ @FastNative
+ private static native long nCreatePathData(long nativePtr);
+ @FastNative
private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
long toDataPtr, float fraction);
+ @FastNative
private static native void nFinalize(long nativePtr);
+ @FastNative
private static native boolean nCanMorph(long fromDataPtr, long toDataPtr);
+ @FastNative
private static native void nSetPathData(long outDataPtr, long fromDataPtr);
}
diff --git a/core/java/android/util/proto/EncodedBuffer.java b/core/java/android/util/proto/EncodedBuffer.java
new file mode 100644
index 0000000..ed38e6f
--- /dev/null
+++ b/core/java/android/util/proto/EncodedBuffer.java
@@ -0,0 +1,678 @@
+/*
+ * Copyright (C) 2012 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.util.proto;
+
+import android.annotation.TestApi;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * A stream of bytes containing a read pointer and a write pointer,
+ * backed by a set of fixed-size buffers. There are write functions for the
+ * primitive types stored by protocol buffers, but none of the logic
+ * for tags, inner objects, or any of that.
+ *
+ * Terminology:
+ * *Pos: Position in the whole data set (as if it were a single buffer).
+ * *Index: Position within a buffer.
+ * *BufIndex: Index of a buffer within the mBuffers list
+ * @hide
+ */
+@TestApi
+public final class EncodedBuffer {
+ private static final String TAG = "EncodedBuffer";
+
+ private final ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
+
+ private final int mChunkSize;
+
+ /**
+ * The number of buffers in mBuffers. Stored separately to avoid the extra
+ * function call to size() everywhere for bounds checking.
+ */
+ private int mBufferCount;
+
+ /**
+ * The buffer we are currently writing to.
+ */
+ private byte[] mWriteBuffer;
+
+ /**
+ * The index into mWriteBuffer that we will write to next.
+ * It may point to the end of the buffer, in which case,
+ * the NEXT write will allocate a new buffer.
+ */
+ private int mWriteIndex;
+
+ /**
+ * The index of mWriteBuffer in mBuffers.
+ */
+ private int mWriteBufIndex;
+
+ /**
+ * The buffer we are currently reading from.
+ */
+ private byte[] mReadBuffer;
+
+ /**
+ * The index of mReadBuffer in mBuffers.
+ */
+ private int mReadBufIndex;
+
+ /**
+ * The index into mReadBuffer that we will read from next.
+ * It may point to the end of the buffer, in which case,
+ * the NEXT read will advance to the next buffer.
+ */
+ private int mReadIndex;
+
+ /**
+ * The amount of data in the last buffer.
+ */
+ private int mReadLimit = -1;
+
+ /**
+ * How much data there is total.
+ */
+ private int mReadableSize = -1;
+
+ public EncodedBuffer() {
+ this(0);
+ }
+
+ /**
+ * Construct an EncodedBuffer object.
+ *
+ * @param chunkSize The size of the buffers to use. If chunkSize <= 0, a default
+ * size will be used instead.
+ */
+ public EncodedBuffer(int chunkSize) {
+ if (chunkSize <= 0) {
+ chunkSize = 8 * 1024;
+ }
+ mChunkSize = chunkSize;
+ mWriteBuffer = new byte[mChunkSize];
+ mBuffers.add(mWriteBuffer);
+ mBufferCount = 1;
+ }
+
+ //
+ // Buffer management.
+ //
+
+ /**
+ * Rewind the read and write pointers, and record how much data was last written.
+ */
+ public void startEditing() {
+ mReadableSize = ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
+ mReadLimit = mWriteIndex;
+
+ mWriteBuffer = mBuffers.get(0);
+ mWriteIndex = 0;
+ mWriteBufIndex = 0;
+
+ mReadBuffer = mWriteBuffer;
+ mReadBufIndex = 0;
+ mReadIndex = 0;
+ }
+
+ /**
+ * Rewind the read pointer. Don't touch the write pointer.
+ */
+ public void rewindRead() {
+ mReadBuffer = mBuffers.get(0);
+ mReadBufIndex = 0;
+ mReadIndex = 0;
+ }
+
+ /**
+ * Only valid after startEditing. Returns -1 before that.
+ */
+ public int getReadableSize() {
+ return mReadableSize;
+ }
+
+ //
+ // Reading from the read position.
+ //
+
+ /**
+ * Only valid after startEditing.
+ */
+ public int getReadPos() {
+ return ((mReadBufIndex) * mChunkSize) + mReadIndex;
+ }
+
+ /**
+ * Skip over _amount_ bytes.
+ */
+ public void skipRead(int amount) {
+ if (amount < 0) {
+ throw new RuntimeException("skipRead with negative amount=" + amount);
+ }
+ if (amount == 0) {
+ return;
+ }
+ if (amount <= mChunkSize - mReadIndex) {
+ mReadIndex += amount;
+ } else {
+ amount -= mChunkSize - mReadIndex;
+ mReadIndex = amount % mChunkSize;
+ if (mReadIndex == 0) {
+ mReadIndex = mChunkSize;
+ mReadBufIndex += (amount / mChunkSize);
+ } else {
+ mReadBufIndex += 1 + (amount / mChunkSize);
+ }
+ mReadBuffer = mBuffers.get(mReadBufIndex);
+ }
+ }
+
+ /**
+ * Read one byte from the stream and advance the read pointer.
+ *
+ * @throws IndexOutOfBoundsException if the read point is past the end of
+ * the buffer or past the read limit previously set by startEditing().
+ */
+ public byte readRawByte() {
+ if (mReadBufIndex > mBufferCount
+ || (mReadBufIndex == mBufferCount - 1 && mReadIndex >= mReadLimit)) {
+ throw new IndexOutOfBoundsException("Trying to read too much data"
+ + " mReadBufIndex=" + mReadBufIndex + " mBufferCount=" + mBufferCount
+ + " mReadIndex=" + mReadIndex + " mReadLimit=" + mReadLimit);
+ }
+ if (mReadIndex >= mChunkSize) {
+ mReadBufIndex++;
+ mReadBuffer = mBuffers.get(mReadBufIndex);
+ mReadIndex = 0;
+ }
+ return mReadBuffer[mReadIndex++];
+ }
+
+ /**
+ * Read an unsigned varint. The value will be returend in a java signed long.
+ */
+ public long readRawUnsigned() {
+ int bits = 0;
+ long result = 0;
+ while (true) {
+ final byte b = readRawByte();
+ result |= ((long)(b & 0x7F)) << bits;
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ bits += 7;
+ if (bits > 64) {
+ throw new ProtoParseException("Varint too long -- " + getDebugString());
+ }
+ }
+ }
+
+ /**
+ * Read 32 little endian bits from the stream.
+ */
+ public int readRawFixed32() {
+ return (readRawByte() & 0x0ff)
+ | ((readRawByte() & 0x0ff) << 8)
+ | ((readRawByte() & 0x0ff) << 16)
+ | ((readRawByte() & 0x0ff) << 24);
+ }
+
+ //
+ // Writing at a the end of the stream.
+ //
+
+ /**
+ * Advance to the next write buffer, allocating it if necessary.
+ *
+ * Must be called immediately <b>before</b> the next write, not after a write,
+ * so that a dangling empty buffer is not created. Doing so will interfere
+ * with the expectation that mWriteIndex will point past the end of the buffer
+ * until the next read happens.
+ */
+ private void nextWriteBuffer() {
+ mWriteBufIndex++;
+ if (mWriteBufIndex >= mBufferCount) {
+ mWriteBuffer = new byte[mChunkSize];
+ mBuffers.add(mWriteBuffer);
+ mBufferCount++;
+ } else {
+ mWriteBuffer = mBuffers.get(mWriteBufIndex);
+ }
+ mWriteIndex = 0;
+ }
+
+ /**
+ * Write a single byte to the stream.
+ */
+ public void writeRawByte(byte val) {
+ if (mWriteIndex >= mChunkSize) {
+ nextWriteBuffer();
+ }
+ mWriteBuffer[mWriteIndex++] = val;
+ }
+
+ /**
+ * Return how many bytes a 32 bit unsigned varint will take when written to the stream.
+ */
+ public static int getRawVarint32Size(int val) {
+ if ((val & (0xffffffff << 7)) == 0) return 1;
+ if ((val & (0xffffffff << 14)) == 0) return 2;
+ if ((val & (0xffffffff << 21)) == 0) return 3;
+ if ((val & (0xffffffff << 28)) == 0) return 4;
+ return 5;
+ }
+
+ /**
+ * Write an unsigned varint to the stream. A signed value would need to take 10 bytes.
+ *
+ * @param val treated as unsigned.
+ */
+ public void writeRawVarint32(int val) {
+ while (true) {
+ if ((val & ~0x7F) == 0) {
+ writeRawByte((byte)val);
+ return;
+ } else {
+ writeRawByte((byte)((val & 0x7F) | 0x80));
+ val >>>= 7;
+ }
+ }
+ }
+
+ /**
+ * Return how many bytes a 32 bit signed zig zag value will take when written to the stream.
+ */
+ public static int getRawZigZag32Size(int val) {
+ return getRawVarint32Size(zigZag32(val));
+ }
+
+ /**
+ * Write a zig-zag encoded value.
+ *
+ * @param val treated as signed
+ */
+ public void writeRawZigZag32(int val) {
+ writeRawVarint32(zigZag32(val));
+ }
+
+ /**
+ * Return how many bytes a 64 bit varint will take when written to the stream.
+ */
+ public static int getRawVarint64Size(long val) {
+ if ((val & (0xffffffffffffffffL << 7)) == 0) return 1;
+ if ((val & (0xffffffffffffffffL << 14)) == 0) return 2;
+ if ((val & (0xffffffffffffffffL << 21)) == 0) return 3;
+ if ((val & (0xffffffffffffffffL << 28)) == 0) return 4;
+ if ((val & (0xffffffffffffffffL << 35)) == 0) return 5;
+ if ((val & (0xffffffffffffffffL << 42)) == 0) return 6;
+ if ((val & (0xffffffffffffffffL << 49)) == 0) return 7;
+ if ((val & (0xffffffffffffffffL << 56)) == 0) return 8;
+ if ((val & (0xffffffffffffffffL << 63)) == 0) return 9;
+ return 10;
+ }
+
+ /**
+ * Write a 64 bit varint to the stream.
+ */
+ public void writeRawVarint64(long val) {
+ while (true) {
+ if ((val & ~0x7FL) == 0) {
+ writeRawByte((byte)val);
+ return;
+ } else {
+ writeRawByte((byte)((val & 0x7F) | 0x80));
+ val >>>= 7;
+ }
+ }
+ }
+
+ /**
+ * Return how many bytes a signed 64 bit zig zag value will take when written to the stream.
+ */
+ public static int getRawZigZag64Size(long val) {
+ return getRawVarint64Size(zigZag64(val));
+ }
+
+ /**
+ * Write a 64 bit signed zig zag value to the stream.
+ */
+ public void writeRawZigZag64(long val) {
+ writeRawVarint64(zigZag64(val));
+ }
+
+ /**
+ * Write 4 little endian bytes to the stream.
+ */
+ public void writeRawFixed32(int val) {
+ writeRawByte((byte)(val));
+ writeRawByte((byte)(val >> 8));
+ writeRawByte((byte)(val >> 16));
+ writeRawByte((byte)(val >> 24));
+ }
+
+ /**
+ * Write 8 little endian bytes to the stream.
+ */
+ public void writeRawFixed64(long val) {
+ writeRawByte((byte)(val));
+ writeRawByte((byte)(val >> 8));
+ writeRawByte((byte)(val >> 16));
+ writeRawByte((byte)(val >> 24));
+ writeRawByte((byte)(val >> 32));
+ writeRawByte((byte)(val >> 40));
+ writeRawByte((byte)(val >> 48));
+ writeRawByte((byte)(val >> 56));
+ }
+
+ /**
+ * Write a buffer to the stream. Writes nothing if val is null or zero-length.
+ */
+ public void writeRawBuffer(byte[] val) {
+ if (val != null && val.length > 0) {
+ writeRawBuffer(val, 0, val.length);
+ }
+ }
+
+ /**
+ * Write part of an array of bytes.
+ */
+ public void writeRawBuffer(byte[] val, int offset, int length) {
+ if (val == null) {
+ return;
+ }
+ // Write up to the amount left in the first chunk to write.
+ int amt = length < (mChunkSize - mWriteIndex) ? length : (mChunkSize - mWriteIndex);
+ if (amt > 0) {
+ System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
+ mWriteIndex += amt;
+ length -= amt;
+ offset += amt;
+ }
+ while (length > 0) {
+ // We know we're now at the beginning of a chunk
+ nextWriteBuffer();
+ amt = length < mChunkSize ? length : mChunkSize;
+ System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
+ mWriteIndex += amt;
+ length -= amt;
+ offset += amt;
+ }
+ }
+
+ /**
+ * Copies data _size_ bytes of data within this buffer from _srcOffset_
+ * to the current write position. Like memmov but handles the chunked buffer.
+ */
+ public void writeFromThisBuffer(int srcOffset, int size) {
+ if (mReadLimit < 0) {
+ throw new IllegalStateException("writeFromThisBuffer before startEditing");
+ }
+ if (srcOffset < getWritePos()) {
+ throw new IllegalArgumentException("Can only move forward in the buffer --"
+ + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
+ }
+ if (srcOffset + size > mReadableSize) {
+ throw new IllegalArgumentException("Trying to move more data than there is --"
+ + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
+ }
+ if (size == 0) {
+ return;
+ }
+ if (srcOffset == ((mWriteBufIndex) * mChunkSize) + mWriteIndex /* write pos */) {
+ // Writing to the same location. Just advance the write pointer. We already
+ // checked that size is in bounds, so we don't need to do any more range
+ // checking.
+ if (size <= mChunkSize - mWriteIndex) {
+ mWriteIndex += size;
+ } else {
+ size -= mChunkSize - mWriteIndex;
+ mWriteIndex = size % mChunkSize;
+ if (mWriteIndex == 0) {
+ // Roll it back so nextWriteBuffer can do its job
+ // on the next call (also makes mBuffers.get() not
+ // fail if we're at the end).
+ mWriteIndex = mChunkSize;
+ mWriteBufIndex += (size / mChunkSize);
+ } else {
+ mWriteBufIndex += 1 + (size / mChunkSize);
+ }
+ mWriteBuffer = mBuffers.get(mWriteBufIndex);
+ }
+ } else {
+ // Loop through the buffer, copying as much as we can each time.
+ // We already bounds checked so we don't need to do it again here,
+ // and nextWriteBuffer will never allocate.
+ int readBufIndex = srcOffset / mChunkSize;
+ byte[] readBuffer = mBuffers.get(readBufIndex);
+ int readIndex = srcOffset % mChunkSize;
+ while (size > 0) {
+ if (mWriteIndex >= mChunkSize) {
+ nextWriteBuffer();
+ }
+ if (readIndex >= mChunkSize) {
+ readBufIndex++;
+ readBuffer = mBuffers.get(readBufIndex);
+ readIndex = 0;
+ }
+ final int spaceInWriteBuffer = mChunkSize - mWriteIndex;
+ final int availableInReadBuffer = mChunkSize - readIndex;
+ final int amt = Math.min(size, Math.min(spaceInWriteBuffer, availableInReadBuffer));
+ System.arraycopy(readBuffer, readIndex, mWriteBuffer, mWriteIndex, amt);
+ mWriteIndex += amt;
+ readIndex += amt;
+ size -= amt;
+ }
+ }
+ }
+
+ //
+ // Writing at a particular location.
+ //
+
+ /**
+ * Returns the index into the virtual array of the write pointer.
+ */
+ public int getWritePos() {
+ return ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
+ }
+
+ /**
+ * Resets the write pointer to a virtual location as returned by getWritePos.
+ */
+ public void rewindWriteTo(int writePos) {
+ if (writePos > getWritePos()) {
+ throw new RuntimeException("rewindWriteTo only can go backwards" + writePos);
+ }
+ mWriteBufIndex = writePos / mChunkSize;
+ mWriteIndex = writePos % mChunkSize;
+ if (mWriteIndex == 0 && mWriteBufIndex != 0) {
+ // Roll back so nextWriteBuffer can do its job on the next call
+ // but at the first write we're at 0.
+ mWriteIndex = mChunkSize;
+ mWriteBufIndex--;
+ }
+ mWriteBuffer = mBuffers.get(mWriteBufIndex);
+ }
+
+ /**
+ * Read a 32 bit value from the stream.
+ *
+ * Doesn't touch or affect mWritePos.
+ */
+ public int getRawFixed32At(int pos) {
+ return (0x00ff & (int)mBuffers.get(pos / mChunkSize)[pos % mChunkSize])
+ | ((0x0ff & (int)mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize]) << 8)
+ | ((0x0ff & (int)mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize]) << 16)
+ | ((0x0ff & (int)mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize]) << 24);
+ }
+
+ /**
+ * Overwrite a 32 bit value in the stream.
+ *
+ * Doesn't touch or affect mWritePos.
+ */
+ public void editRawFixed32(int pos, int val) {
+ mBuffers.get(pos / mChunkSize)[pos % mChunkSize] = (byte)(val);
+ mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize] = (byte)(val >> 8);
+ mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize] = (byte)(val >> 16);
+ mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize] = (byte)(val >> 24);
+ }
+
+ //
+ // Zigging and zagging
+ //
+
+ /**
+ * Zig-zag encode a 32 bit value.
+ */
+ private static int zigZag32(int val) {
+ return (val << 1) ^ (val >> 31);
+ }
+
+ /**
+ * Zig-zag encode a 64 bit value.
+ */
+ private static long zigZag64(long val) {
+ return (val << 1) ^ (val >> 63);
+ }
+
+ //
+ // Debugging / testing
+ //
+ // VisibleForTesting
+
+ /**
+ * Get a copy of the first _size_ bytes of data. This is not range
+ * checked, and if the bounds are outside what has been written you will
+ * get garbage and if it is outside the buffers that have been allocated,
+ * you will get an exception.
+ */
+ public byte[] getBytes(int size) {
+ final byte[] result = new byte[size];
+
+ final int bufCount = size / mChunkSize;
+ int bufIndex;
+ int writeIndex = 0;
+
+ for (bufIndex=0; bufIndex<bufCount; bufIndex++) {
+ System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, mChunkSize);
+ writeIndex += mChunkSize;
+ }
+
+ final int lastSize = size - (bufCount * mChunkSize);
+ if (lastSize > 0) {
+ System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, lastSize);
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the number of chunks allocated.
+ */
+ // VisibleForTesting
+ public int getChunkCount() {
+ return mBuffers.size();
+ }
+
+ /**
+ * Get the write position inside the current write chunk.
+ */
+ // VisibleForTesting
+ public int getWriteIndex() {
+ return mWriteIndex;
+ }
+
+ /**
+ * Get the index of the current write chunk in the list of chunks.
+ */
+ // VisibleForTesting
+ public int getWriteBufIndex() {
+ return mWriteBufIndex;
+ }
+
+ /**
+ * Return debugging information about this EncodedBuffer object.
+ */
+ public String getDebugString() {
+ return "EncodedBuffer( mChunkSize=" + mChunkSize + " mBuffers.size=" + mBuffers.size()
+ + " mBufferCount=" + mBufferCount + " mWriteIndex=" + mWriteIndex
+ + " mWriteBufIndex=" + mWriteBufIndex + " mReadBufIndex=" + mReadBufIndex
+ + " mReadIndex=" + mReadIndex + " mReadableSize=" + mReadableSize
+ + " mReadLimit=" + mReadLimit + " )";
+ }
+
+ /**
+ * Print the internal buffer chunks.
+ */
+ public void dumpBuffers(String tag) {
+ final int N = mBuffers.size();
+ int start = 0;
+ for (int i=0; i<N; i++) {
+ start += dumpByteString(tag, "{" + i + "} ", start, mBuffers.get(i));
+ }
+ }
+
+ /**
+ * Print the internal buffer chunks.
+ */
+ public static void dumpByteString(String tag, String prefix, byte[] buf) {
+ dumpByteString(tag, prefix, 0, buf);
+ }
+
+ /**
+ * Print the internal buffer chunks.
+ */
+ private static int dumpByteString(String tag, String prefix, int start, byte[] buf) {
+ StringBuffer sb = new StringBuffer();
+ final int length = buf.length;
+ final int lineLen = 16;
+ int i;
+ for (i=0; i<length; i++) {
+ if (i % lineLen == 0) {
+ if (i != 0) {
+ Log.d(tag, sb.toString());
+ sb = new StringBuffer();
+ }
+ sb.append(prefix);
+ sb.append('[');
+ sb.append(start + i);
+ sb.append(']');
+ sb.append(' ');
+ } else {
+ sb.append(' ');
+ }
+ byte b = buf[i];
+ byte c = (byte)((b >> 4) & 0x0f);
+ if (c < 10) {
+ sb.append((char)('0' + c));
+ } else {
+ sb.append((char)('a' - 10 + c));
+ }
+ byte d = (byte)(b & 0x0f);
+ if (d < 10) {
+ sb.append((char)('0' + d));
+ } else {
+ sb.append((char)('a' - 10 + d));
+ }
+ }
+ Log.d(tag, sb.toString());
+ return length;
+ }
+}
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
new file mode 100644
index 0000000..8f99399
--- /dev/null
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -0,0 +1,1599 @@
+/*
+ * Copyright (C) 2012 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.util.proto;
+
+import android.annotation.TestApi;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+/**
+ * Class to write to a protobuf stream.
+ *
+ * Each write method takes an ID code from the protoc generated classes
+ * and the value to write. To make a nested object, call startObject
+ * and then endObject when you are done.
+ *
+ * The ID codes have type information embedded into them, so if you call
+ * the incorrect function you will get an IllegalArgumentException.
+ *
+ * To retrieve the encoded protobuf stream, call getBytes().
+ *
+ * TODO: Add a constructor that takes an OutputStream and write to that
+ * stream as the top-level objects are finished.
+ *
+ * @hide
+ */
+
+/* IMPLEMENTATION NOTES
+ *
+ * Because protobuf has inner values, and they are length prefixed, and
+ * those sizes themselves are stored with a variable length encoding, it
+ * is impossible to know how big an object will be in a single pass.
+ *
+ * The traditional way is to copy the in-memory representation of an object
+ * into the generated proto Message objects, do a traversal of those to
+ * cache the size, and then write the size-prefixed buffers.
+ *
+ * We are trying to avoid too much generated code here, but this class still
+ * needs to have a somewhat sane API. We can't have the multiple passes be
+ * done by the calling code. In addition, we want to avoid the memory high
+ * water mark of duplicating all of the values into the traditional in-memory
+ * Message objects. We need to find another way.
+ *
+ * So what we do here is to let the calling code write the data into a
+ * byte[] (actually a collection of them wrapped in the EncodedBuffer) class,
+ * but not do the varint encoding of the sub-message sizes. Then, we do a
+ * recursive traversal of the buffer itself, calculating the sizes (which are
+ * then knowable, although still not the actual sizes in the buffer because of
+ * possible further nesting). Then we do a third pass, compacting the
+ * buffer and varint encoding the sizes.
+ *
+ * This gets us a relatively small number number of fixed-size allocations,
+ * which is less likely to cause memory fragmentation or churn the GC, and
+ * the same number of data copies as would have gotten with setting it
+ * field-by-field in generated code, and no code bloat from generated code.
+ * The final data copy is also done with System.arraycopy, which will be
+ * more efficient, in general, than doing the individual fields twice (as in
+ * the traditional way).
+ *
+ * To accomplish the multiple passes, whenever we write a
+ * WIRE_TYPE_LENGTH_DELIMITED field, we write the size occupied in our
+ * buffer as a fixed 32 bit int (called childRawSize), not variable length
+ * one. We reserve another 32 bit slot for the computed size (called
+ * childEncodedSize). If we know the size up front, as we do for strings
+ * and byte[], then we also put that into childEncodedSize, if we don't, we
+ * write the negative of childRawSize, as a sentiel that we need to
+ * compute it during the second pass and recursively compact it during the
+ * third pass.
+ *
+ * Unsgigned size varints can be up to five bytes long, but we reserve eight
+ * bytes for overhead, so we know that when we compact the buffer, there
+ * will always be space for the encoded varint.
+ *
+ * When we can figure out the size ahead of time, we do, in order
+ * to save overhead with recalculating it, and with the later arraycopy.
+ *
+ * During the period between when the caller has called startObject, but
+ * not yet called endObject, we maintain a linked list of the tokens
+ * returned by startObject, stored in those 8 bytes of size storage space.
+ * We use that linked list of tokens to ensure that the caller has
+ * correctly matched pairs of startObject and endObject calls, and issue
+ * errors if they are not matched.
+ */
+@TestApi
+public final class ProtoOutputStream {
+ public static final String TAG = "ProtoOutputStream";
+
+ public static final int FIELD_ID_SHIFT = 3;
+ public static final int WIRE_TYPE_MASK = (1<<FIELD_ID_SHIFT)-1;
+ public static final int FIELD_ID_MASK = ~WIRE_TYPE_MASK;
+
+ public static final int WIRE_TYPE_VARINT = 0;
+ public static final int WIRE_TYPE_FIXED64 = 1;
+ public static final int WIRE_TYPE_LENGTH_DELIMITED = 2;
+ public static final int WIRE_TYPE_START_GROUP = 3;
+ public static final int WIRE_TYPE_END_GROUP = 4;
+ public static final int WIRE_TYPE_FIXED32 = 5;
+
+ /**
+ * Position of the field type in a (long) fieldId.
+ */
+ public static final int FIELD_TYPE_SHIFT = 32;
+
+ /**
+ * Mask for the field types stored in a fieldId. Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+ public static final long FIELD_TYPE_MASK = 0x0ffL << FIELD_TYPE_SHIFT;
+
+ public static final long FIELD_TYPE_UNKNOWN = 0;
+
+ public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+
+ private static final String[] FIELD_TYPE_NAMES = new String[] {
+ "Double",
+ "Float",
+ "Int32",
+ "Int64",
+ "UInt32",
+ "UInt64",
+ "SInt32",
+ "SInt64",
+ "Fixed32",
+ "Fixed64",
+ "SFixed32",
+ "SFixed64",
+ "Bool",
+ "String",
+ "Bytes",
+ "Enum",
+ "Object",
+ };
+
+ //
+ // FieldId flags for whether the field is single, repeated or packed.
+ //
+ public static final int FIELD_COUNT_SHIFT = 40;
+ public static final long FIELD_COUNT_MASK = 0x0fL << FIELD_COUNT_SHIFT;
+
+ public static final long FIELD_COUNT_UNKNOWN = 0;
+ public static final long FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+ public static final long FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+ public static final long FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+ /**
+ * Our buffer.
+ */
+ private EncodedBuffer mBuffer;
+
+ /**
+ * Current nesting depth of startObject calls.
+ */
+ private int mDepth;
+
+ /**
+ * An ID given to objects and returned in the token from startObject
+ * and stored in the buffer until endObject is called, where the two
+ * are checked. Starts at -1 and becomes more negative, so the values
+ * aren't likely to alias with the size it will be overwritten with,
+ * which tend to be small, and we will be more likely to catch when
+ * the caller of endObject uses a stale token that they didn't intend
+ * to (e.g. copy and paste error).
+ */
+ private int mNextObjectId = -1;
+
+ /**
+ * The object token we are expecting in endObject. If another call to
+ * startObject happens, this is written to that location, which gives
+ * us a stack, stored in the space for the as-yet unused size fields.
+ */
+ private long mExpectedObjectToken;
+
+ /**
+ * Index in mBuffer that we should start copying from on the next
+ * pass of compaction.
+ */
+ private int mCopyBegin;
+
+ /**
+ * Whether we've already compacted
+ */
+ private boolean mCompacted;
+
+ /**
+ * Construct a ProtoOutputStream with the default chunk size.
+ */
+ public ProtoOutputStream() {
+ this(0);
+ }
+
+ /**
+ * Construct a ProtoOutputStream with the given chunk size.
+ */
+ public ProtoOutputStream(int chunkSize) {
+ mBuffer = new EncodedBuffer(chunkSize);
+ }
+
+ //
+ // proto3 type: double
+ // java type: double
+ // encoding: fixed64
+ // wire type: WIRE_TYPE_FIXED64
+ //
+
+ /**
+ * Write a single proto "double" type field value.
+ */
+ public void writeDouble(long fieldId, double val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
+ }
+ }
+
+ /**
+ * Write a single repeated proto "double" type field value.
+ */
+ public void writeRepeatedDouble(long fieldId, double val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_DOUBLE);
+
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
+ }
+
+ /**
+ * Write a list of packed proto "double" type field values.
+ */
+ public void writePackedDouble(long fieldId, double[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_DOUBLE);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 8);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed64(Double.doubleToLongBits(val[i]));
+ }
+ }
+ }
+
+ //
+ // proto3 type: float
+ // java type: float
+ // encoding: fixed32
+ // wire type: WIRE_TYPE_FIXED32
+ //
+
+ /**
+ * Write a single proto "float" type field value.
+ */
+ public void writeFloat(long fieldId, float val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(Float.floatToIntBits(val));
+ }
+ }
+
+ /**
+ * Write a single repeated proto "float" type field value.
+ */
+ public void writeRepeatedFloat(long fieldId, float val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FLOAT);
+
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(Float.floatToIntBits(val));
+ }
+
+ /**
+ * Write a list of packed proto "float" type field value.
+ */
+ public void writePackedFloat(long fieldId, float[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FLOAT);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 4);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed32(Float.floatToIntBits(val[i]));
+ }
+ }
+ }
+
+ //
+ // proto3 type: int32
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Writes a java int as an usigned varint.
+ *
+ * The unadorned int32 type in protobuf is unfortunate because it
+ * is stored in memory as a signed value, but encodes as unsigned
+ * varints, which are formally always longs. So here, we encode
+ * negative values as 64 bits, which will get the sign-extension,
+ * and positive values as 32 bits, which saves a marginal amount
+ * of work in that it processes ints instead of longs.
+ */
+ private void writeUnsignedVarintFromSignedInt(int val) {
+ if (val >= 0) {
+ mBuffer.writeRawVarint32(val);
+ } else {
+ mBuffer.writeRawVarint64(val);
+ }
+ }
+
+ /**
+ * Write a single proto "int32" type field value.
+ *
+ * Note that these are stored in memory as signed values and written as unsigned
+ * varints, which if negative, are 10 bytes long. If you know the data is likely
+ * to be negative, use "sint32".
+ */
+ public void writeInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "int32" type field value.
+ *
+ * Note that these are stored in memory as signed values and written as unsigned
+ * varints, which if negative, are 10 bytes long. If you know the data is likely
+ * to be negative, use "sint32".
+ */
+ public void writeRepeatedInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT32);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+
+ /**
+ * Write a list of packed proto "int32" type field value.
+ *
+ * Note that these are stored in memory as signed values and written as unsigned
+ * varints, which if negative, are 10 bytes long. If you know the data is likely
+ * to be negative, use "sint32".
+ */
+ public void writePackedInt32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ final int v = val[i];
+ size += v >= 0 ? EncodedBuffer.getRawVarint32Size(v) : 10;
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ writeUnsignedVarintFromSignedInt(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: int64
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "int64" type field value.
+ */
+ public void writeInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "int64" type field value.
+ */
+ public void writeRepeatedInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT64);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+
+ /**
+ * Write a list of packed proto "int64" type field value.
+ */
+ public void writePackedInt64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawVarint64Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawVarint64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: uint32
+ // java type: int
+ // signed/unsigned: unsigned
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "uint32" type field value.
+ */
+ public void writeUInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "uint32" type field value.
+ */
+ public void writeRepeatedUInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT32);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint32(val);
+ }
+
+ /**
+ * Write a list of packed proto "uint32" type field value.
+ */
+ public void writePackedUInt32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawVarint32Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawVarint32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: uint64
+ // java type: int
+ // signed/unsigned: unsigned
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "uint64" type field value.
+ */
+ public void writeUInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+ }
+
+ /**
+ * Write a single proto "uint64" type field value.
+ */
+ public void writeRepeatedUInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT64);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+
+ /**
+ * Write a single proto "uint64" type field value.
+ */
+ public void writePackedUInt64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawVarint64Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawVarint64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sint32
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: zig-zag
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "sint32" type field value.
+ */
+ public void writeSInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sint32" type field value.
+ */
+ public void writeRepeatedSInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT32);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag32(val);
+ }
+
+ /**
+ * Write a list of packed proto "sint32" type field value.
+ */
+ public void writePackedSInt32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawZigZag32Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawZigZag32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sint64
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: zig-zag
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "sint64" type field value.
+ */
+ public void writeSInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sint64" type field value.
+ */
+ public void writeRepeatedSInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT64);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag64(val);
+ }
+
+ /**
+ * Write a list of packed proto "sint64" type field value.
+ */
+ public void writePackedSInt64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawZigZag64Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawZigZag64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: fixed32
+ // java type: int
+ // encoding: little endian
+ // wire type: WIRE_TYPE_FIXED32
+ //
+
+ /**
+ * Write a single proto "fixed32" type field value.
+ */
+ public void writeFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "fixed32" type field value.
+ */
+ public void writeRepeatedFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED32);
+
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+
+ /**
+ * Write a list of packed proto "fixed32" type field value.
+ */
+ public void writePackedFixed32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 4);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: fixed64
+ // java type: long
+ // encoding: fixed64
+ // wire type: WIRE_TYPE_FIXED64
+ //
+
+ /**
+ * Write a single proto "fixed64" type field value.
+ */
+ public void writeFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "fixed64" type field value.
+ */
+ public void writeRepeatedFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED64);
+
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+
+ /**
+ * Write a list of packed proto "fixed64" type field value.
+ */
+ public void writePackedFixed64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 8);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sfixed32
+ // java type: int
+ // encoding: little endian
+ // wire type: WIRE_TYPE_FIXED32
+ //
+ /**
+ * Write a single proto "sfixed32" type field value.
+ */
+ public void writeSFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sfixed32" type field value.
+ */
+ public void writeRepeatedSFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED32);
+
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+
+ /**
+ * Write a list of packed proto "sfixed32" type field value.
+ */
+ public void writePackedSFixed32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 4);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sfixed64
+ // java type: long
+ // encoding: little endian
+ // wire type: WIRE_TYPE_FIXED64
+ //
+
+ /**
+ * Write a single proto "sfixed64" type field value.
+ */
+ public void writeSFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sfixed64" type field value.
+ */
+ public void writeRepeatedSFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED64);
+
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+
+ /**
+ * Write a list of packed proto "sfixed64" type field value.
+ */
+ public void writePackedSFixed64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 8);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: bool
+ // java type: boolean
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "bool" type field value.
+ */
+ public void writeBool(long fieldId, boolean val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL);
+
+ if (val) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ // 0 and 1 are the same as their varint counterparts
+ mBuffer.writeRawByte((byte)1);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "bool" type field value.
+ */
+ public void writeRepeatedBool(long fieldId, boolean val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BOOL);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawByte((byte)(val ? 1 : 0));
+ }
+
+ /**
+ * Write a list of packed proto "bool" type field value.
+ */
+ public void writePackedBool(long fieldId, boolean[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_BOOL);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ // Write the header
+ writeKnownLengthHeader(id, N);
+
+ // Write the data
+ for (int i=0; i<N; i++) {
+ // 0 and 1 are the same as their varint counterparts
+ mBuffer.writeRawByte((byte)(val[i] ? 1 : 0));
+ }
+ }
+ }
+
+ //
+ // proto3 type: string
+ // java type: String
+ // encoding: utf-8
+ // wire type: WIRE_TYPE_LENGTH_DELIMITED
+ //
+
+ /**
+ * Write a single proto "string" type field value.
+ */
+ public void writeString(long fieldId, String val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);
+
+ if (val != null && val.length() > 0) {
+ writeUtf8String(id, val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "string" type field value.
+ */
+ public void writeRepeatedString(long fieldId, String val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);
+
+ if (val == null || val.length() == 0) {
+ writeKnownLengthHeader(id, 0);
+ } else {
+ writeUtf8String(id, val);
+ }
+ }
+
+ /**
+ * Write a list of packed proto "string" type field value.
+ */
+ private void writeUtf8String(int id, String val) {
+ // TODO: Is it worth converting by hand in order to not allocate?
+ try {
+ final byte[] buf = val.getBytes("UTF-8");
+ writeKnownLengthHeader(id, buf.length);
+ mBuffer.writeRawBuffer(buf);
+ } catch (UnsupportedEncodingException ex) {
+ throw new RuntimeException("not possible");
+ }
+ }
+
+ //
+ // proto3 type: bytes
+ // java type: byte[]
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "bytes" type field value.
+ */
+ public void writeBytes(long fieldId, byte[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);
+
+ if (val != null && val.length > 0) {
+ writeKnownLengthHeader(id, val.length);
+ mBuffer.writeRawBuffer(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "bytes" type field value.
+ */
+ public void writeRepeatedBytes(long fieldId, byte[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);
+
+ writeKnownLengthHeader(id, val == null ? 0 : val.length);
+ mBuffer.writeRawBuffer(val);
+ }
+
+ //
+ // proto3 type: enum
+ // java type: int
+ // signed/unsigned: unsigned
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto enum type field value.
+ */
+ public void writeEnum(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto enum type field value.
+ */
+ public void writeRepeatedEnum(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_ENUM);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+
+ /**
+ * Write a list of packed proto enum type field value.
+ */
+ public void writePackedEnum(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_ENUM);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ final int v = val[i];
+ size += v >= 0 ? EncodedBuffer.getRawVarint32Size(v) : 10;
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ writeUnsignedVarintFromSignedInt(val[i]);
+ }
+ }
+ }
+
+ //
+ // Child objects
+ //
+
+ /**
+ * Make a token.
+ * Bits 61-63 - tag size (So we can go backwards later if the object had not data)
+ * - 3 bits, max value 7, max value needed 5
+ * Bit 60 - true if the object is repeated (lets us require endObject or endRepeatedObject)
+ * Bits 59-51 - depth (For error checking)
+ * - 9 bits, max value 512, when checking, value is masked (if we really
+ * are more than 512 levels deep)
+ * Bits 32-50 - objectId (For error checking)
+ * - 19 bits, max value 524,288. that's a lot of objects. IDs will wrap
+ * because of the overflow, and only the tokens are compared.
+ * Bits 0-31 - offset of the first size field in the buffer.
+ */
+ // VisibleForTesting
+ public static long makeToken(int tagSize, boolean repeated, int depth, int objectId,
+ int sizePos) {
+ return ((0x07L & (long)tagSize) << 61)
+ | (repeated ? (1L << 60) : 0)
+ | (0x01ffL & (long)depth) << 51
+ | (0x07ffffL & (long)objectId) << 32
+ | (0x0ffffffffL & (long)sizePos);
+ }
+
+ /**
+ * Get the encoded tag size from the token.
+ */
+ public static int getTagSizeFromToken(long token) {
+ return (int)(0x7 & (token >> 61));
+ }
+
+ /**
+ * Get whether this is a call to startObject (false) or startRepeatedObject (true).
+ */
+ public static boolean getRepeatedFromToken(long token) {
+ return (0x1 & (token >> 60)) != 0;
+ }
+
+ /**
+ * Get the nesting depth of startObject calls from the token.
+ */
+ public static int getDepthFromToken(long token) {
+ return (int)(0x01ff & (token >> 51));
+ }
+
+ /**
+ * Get the object ID from the token. The object ID is a serial number for the
+ * startObject calls that have happened on this object. The values are truncated
+ * to 9 bits, but that is sufficient for error checking.
+ */
+ public static int getObjectIdFromToken(long token) {
+ return (int)(0x07ffff & (token >> 32));
+ }
+
+ /**
+ * Get the location of the childRawSize (the first 32 bit size field) in this object.
+ */
+ public static int getSizePosFromToken(long token) {
+ return (int)token;
+ }
+
+ /**
+ * Convert the object ID to the ordinal value -- the n-th call to startObject.
+ * The object IDs start at -1 and count backwards, so that the value is unlikely
+ * to alias with an actual size field that had been written.
+ */
+ public static int convertObjectIdToOrdinal(int objectId) {
+ return (-1 & 0x07ffff) - objectId;
+ }
+
+ /**
+ * Return a debugging string of a token.
+ */
+ public static String token2String(long token) {
+ if (token == 0L) {
+ return "Token(0)";
+ } else {
+ return "Token(val=0x" + Long.toHexString(token)
+ + " depth=" + getDepthFromToken(token)
+ + " object=" + convertObjectIdToOrdinal(getObjectIdFromToken(token))
+ + " tagSize=" + getTagSizeFromToken(token)
+ + " sizePos=" + getSizePosFromToken(token)
+ + ')';
+ }
+ }
+
+ /**
+ * Start a child object.
+ *
+ * Returns a token which should be passed to endObject. Calls to endObject must be
+ * nested properly.
+ */
+ public long startObject(long fieldId) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+
+ return startObjectImpl(id, false);
+ }
+
+ /**
+ * End a child object. Pass in the token from the correspoinding startObject call.
+ */
+ public void endObject(long token) {
+ assertNotCompacted();
+
+ endObjectImpl(token, false);
+ }
+
+ /**
+ * Start a repeated child object.
+ *
+ * Returns a token which should be passed to endObject. Calls to endObject must be
+ * nested properly.
+ */
+ public long startRepeatedObject(long fieldId) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+
+ return startObjectImpl(id, true);
+ }
+
+ /**
+ * End a child object. Pass in the token from the correspoinding startRepeatedObject call.
+ */
+ public void endRepeatedObject(long token) {
+ assertNotCompacted();
+
+ endObjectImpl(token, true);
+ }
+
+ /**
+ * Common implementation of startObject and startRepeatedObject.
+ */
+ private long startObjectImpl(final int id, boolean repeated) {
+ writeTag(id, WIRE_TYPE_LENGTH_DELIMITED);
+ final int sizePos = mBuffer.getWritePos();
+ mDepth++;
+ mNextObjectId--;
+
+ // Write the previous token, giving us a stack of expected tokens.
+ // After endObject returns, the first fixed32 becomeschildRawSize (set in endObject)
+ // and the second one becomes childEncodedSize (set in editEncodedSize).
+ mBuffer.writeRawFixed32((int)(mExpectedObjectToken >> 32));
+ mBuffer.writeRawFixed32((int)mExpectedObjectToken);
+
+ long old = mExpectedObjectToken;
+
+ mExpectedObjectToken = makeToken(getTagSize(id), repeated, mDepth, mNextObjectId, sizePos);
+ return mExpectedObjectToken;
+ }
+
+ /**
+ * Common implementation of endObject and endRepeatedObject.
+ */
+ private void endObjectImpl(long token, boolean repeated) {
+ // The upper 32 bits of the token is the depth of startObject /
+ // endObject calls. We could get aritrarily sophisticated, but
+ // that's enough to prevent the common error of missing an
+ // endObject somewhere.
+ // The lower 32 bits of the token is the offset in the buffer
+ // at which to write the size.
+ final int depth = getDepthFromToken(token);
+ final boolean expectedRepeated = getRepeatedFromToken(token);
+ final int sizePos = getSizePosFromToken(token);
+ final int childRawSize = mBuffer.getWritePos() - sizePos - 8;
+
+ if (repeated != expectedRepeated) {
+ if (repeated) {
+ throw new IllegalArgumentException("endRepeatedObject called where endObject should"
+ + " have been");
+ } else {
+ throw new IllegalArgumentException("endObject called where endRepeatedObject should"
+ + " have been");
+ }
+ }
+
+ // Check that we're getting the token and depth that we are expecting.
+ if ((mDepth & 0x01ff) != depth || mExpectedObjectToken != token) {
+ // This text of exception is united tested. That test also implicity checks
+ // that we're tracking the objectIds and depths correctly.
+ throw new IllegalArgumentException("Mismatched startObject/endObject calls."
+ + " Current depth " + mDepth
+ + " token=" + token2String(token)
+ + " expectedToken=" + token2String(mExpectedObjectToken));
+ }
+
+ // Get the next expected token that we stashed away in the buffer.
+ mExpectedObjectToken = (((long)mBuffer.getRawFixed32At(sizePos)) << 32)
+ | (0x0ffffffffL & (long)mBuffer.getRawFixed32At(sizePos+4));
+
+ mDepth--;
+ if (childRawSize > 0) {
+ mBuffer.editRawFixed32(sizePos, -childRawSize);
+ mBuffer.editRawFixed32(sizePos+4, -1);
+ } else if (repeated) {
+ mBuffer.editRawFixed32(sizePos, 0);
+ mBuffer.editRawFixed32(sizePos+4, 0);
+ } else {
+ // The object has no data. Don't include it.
+ mBuffer.rewindWriteTo(sizePos - getTagSizeFromToken(token));
+ }
+ }
+
+ //
+ // Tags
+ //
+
+ /**
+ * Combine a fieldId (the field keys in the proto file) and the field flags.
+ * Mostly useful for testing because the generated code contains the fieldId
+ * constants.
+ */
+ public static long makeFieldId(int id, long fieldFlags) {
+ return fieldFlags | (((long)id) & 0x0ffffffffL);
+ }
+
+ /**
+ * Validates that the fieldId providied is of the type and count from expectedType.
+ *
+ * The type must match exactly to pass this check.
+ *
+ * The count must match according to this truth table to pass the check:
+ *
+ * expectedFlags
+ * UNKNOWN SINGLE REPEATED PACKED
+ * fieldId
+ * UNKNOWN true false false false
+ * SINGLE x true false false
+ * REPEATED x false true false
+ * PACKED x false true true
+ *
+ * @throws IllegalArgumentException if it is not.
+ *
+ * @return The raw ID of that field.
+ */
+ public static int checkFieldId(long fieldId, long expectedFlags) {
+ final long fieldCount = fieldId & FIELD_COUNT_MASK;
+ final long fieldType = fieldId & FIELD_TYPE_MASK;
+ final long expectedCount = expectedFlags & FIELD_COUNT_MASK;
+ final long expectedType = expectedFlags & FIELD_TYPE_MASK;
+ if (((int)fieldId) == 0) {
+ throw new IllegalArgumentException("Invalid proto field " + (int)fieldId
+ + " fieldId=" + Long.toHexString(fieldId));
+ }
+ if (fieldType != expectedType
+ || !((fieldCount == expectedCount)
+ || (fieldCount == FIELD_COUNT_PACKED
+ && expectedCount == FIELD_COUNT_REPEATED))) {
+ final String countString = getFieldCountString(fieldCount);
+ final String typeString = getFieldTypeString(fieldType);
+ if (typeString != null && countString != null) {
+ final StringBuilder sb = new StringBuilder();
+ if (expectedType == FIELD_TYPE_OBJECT) {
+ sb.append("start");
+ } else {
+ sb.append("write");
+ }
+ sb.append(getFieldCountString(expectedCount));
+ sb.append(getFieldTypeString(expectedType));
+ sb.append(" called for field ");
+ sb.append((int)fieldId);
+ sb.append(" which should be used with ");
+ if (fieldType == FIELD_TYPE_OBJECT) {
+ sb.append("start");
+ } else {
+ sb.append("write");
+ }
+ sb.append(countString);
+ sb.append(typeString);
+ if (fieldCount == FIELD_COUNT_PACKED) {
+ sb.append(" or writeRepeated");
+ sb.append(typeString);
+ }
+ sb.append('.');
+ throw new IllegalArgumentException(sb.toString());
+ } else {
+ final StringBuilder sb = new StringBuilder();
+ if (expectedType == FIELD_TYPE_OBJECT) {
+ sb.append("start");
+ } else {
+ sb.append("write");
+ }
+ sb.append(getFieldCountString(expectedCount));
+ sb.append(getFieldTypeString(expectedType));
+ sb.append(" called with an invalid fieldId: 0x");
+ sb.append(Long.toHexString(fieldId));
+ sb.append(". The proto field ID might be ");
+ sb.append((int)fieldId);
+ sb.append('.');
+ throw new IllegalArgumentException(sb.toString());
+ }
+ }
+ return (int)fieldId;
+ }
+
+ /**
+ * Get the developer-usable name of a field type.
+ */
+ private static String getFieldTypeString(long fieldType) {
+ int index = ((int)((fieldType & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) - 1;
+ if (index >= 0 && index < FIELD_TYPE_NAMES.length) {
+ return FIELD_TYPE_NAMES[index];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the developer-usable name of a field count.
+ */
+ private static String getFieldCountString(long fieldCount) {
+ if (fieldCount == FIELD_COUNT_SINGLE) {
+ return "";
+ } else if (fieldCount == FIELD_COUNT_REPEATED) {
+ return "Repeated";
+ } else if (fieldCount == FIELD_COUNT_PACKED) {
+ return "Packed";
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Return how many bytes an encoded field tag will require.
+ */
+ private static int getTagSize(int id) {
+ return EncodedBuffer.getRawVarint32Size(id << FIELD_ID_SHIFT);
+ }
+
+ /**
+ * Write a field tage to the stream.
+ */
+ public void writeTag(int id, int wireType) {
+ mBuffer.writeRawVarint32((id << FIELD_ID_SHIFT) | wireType);
+ }
+
+ /**
+ * Write the header of a WIRE_TYPE_LENGTH_DELIMITED field for one where
+ * we know the size in advance and do not need to compute and compact.
+ */
+ private void writeKnownLengthHeader(int id, int size) {
+ // Write the tag
+ writeTag(id, WIRE_TYPE_LENGTH_DELIMITED);
+ // Size will be compacted later, but we know the size, so write it,
+ // once for the rawSize and once for the encodedSize.
+ mBuffer.writeRawFixed32(size);
+ mBuffer.writeRawFixed32(size);
+ }
+
+ //
+ // Getting the buffer and compaction
+ //
+
+ /**
+ * Assert that the compact call has not already occured.
+ *
+ * TODO: Will change when we add the OutputStream version of ProtoOutputStream.
+ */
+ private void assertNotCompacted() {
+ if (mCompacted) {
+ throw new IllegalArgumentException("write called after compact");
+ }
+ }
+
+ /**
+ * Finish the encoding of the data, and return a byte[] with
+ * the protobuf formatted data.
+ *
+ * After this call, do not call any of the write* functions. The
+ * behavior is undefined.
+ */
+ public byte[] getBytes() {
+ compactIfNecessary();
+
+ return mBuffer.getBytes(mBuffer.getReadableSize());
+ }
+
+ /**
+ * If the buffer hasn't already had the nested object size fields compacted
+ * and turned into an actual protobuf format, then do so.
+ */
+ private void compactIfNecessary() {
+ if (!mCompacted) {
+ if (mDepth != 0) {
+ throw new IllegalArgumentException("Trying to compact with " + mDepth
+ + " missing calls to endObject");
+ }
+
+ // The buffer must be compacted.
+ mBuffer.startEditing();
+ final int readableSize = mBuffer.getReadableSize();
+
+ // Cache the sizes of the objects
+ editEncodedSize(readableSize);
+
+ // Re-write the buffer with the sizes as proper varints instead
+ // of pairs of uint32s. We know this will always fit in the same
+ // buffer because the pair of uint32s is exactly 8 bytes long, and
+ // the single varint size will be no more than 5 bytes long.
+ mBuffer.rewindRead();
+ compactSizes(readableSize);
+
+ // If there is any data left over that wasn't copied yet, copy it.
+ if (mCopyBegin < readableSize) {
+ mBuffer.writeFromThisBuffer(mCopyBegin, readableSize - mCopyBegin);
+ }
+
+ // Set the new readableSize
+ mBuffer.startEditing();
+
+ // It's not valid to write to this object anymore. The write
+ // pointers are off, and then some of the data would be compacted
+ // and some not.
+ mCompacted = true;
+ }
+ }
+
+ /**
+ * First compaction pass. Iterate through the data, and fill in the
+ * nested object sizes so the next pass can compact them.
+ */
+ private int editEncodedSize(int rawSize) {
+ int objectStart = mBuffer.getReadPos();
+ int objectEnd = objectStart + rawSize;
+ int encodedSize = 0;
+ int tagPos;
+
+ while ((tagPos = mBuffer.getReadPos()) < objectEnd) {
+ int tag = readRawTag();
+ encodedSize += EncodedBuffer.getRawVarint32Size(tag);
+
+ final int wireType = tag & WIRE_TYPE_MASK;
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ encodedSize++;
+ while ((mBuffer.readRawByte() & 0x80) != 0) {
+ encodedSize++;
+ }
+ break;
+ case WIRE_TYPE_FIXED64:
+ encodedSize += 8;
+ mBuffer.skipRead(8);
+ break;
+ case WIRE_TYPE_LENGTH_DELIMITED: {
+ // This object is not of a fixed-size type. So we need to figure
+ // out how big it should be.
+ final int childRawSize = mBuffer.readRawFixed32();
+ final int childEncodedSizePos = mBuffer.getReadPos();
+ int childEncodedSize = mBuffer.readRawFixed32();
+ if (childRawSize >= 0) {
+ // We know the size, just skip ahead.
+ if (childEncodedSize != childRawSize) {
+ throw new RuntimeException("Pre-computed size where the"
+ + " precomputed size and the raw size in the buffer"
+ + " don't match! childRawSize=" + childRawSize
+ + " childEncodedSize=" + childEncodedSize
+ + " childEncodedSizePos=" + childEncodedSizePos);
+ }
+ mBuffer.skipRead(childRawSize);
+ } else {
+ // We need to compute the size. Recurse.
+ childEncodedSize = editEncodedSize(-childRawSize);
+ mBuffer.editRawFixed32(childEncodedSizePos, childEncodedSize);
+ }
+ encodedSize += EncodedBuffer.getRawVarint32Size(childEncodedSize)
+ + childEncodedSize;
+ break;
+ }
+ case WIRE_TYPE_START_GROUP:
+ case WIRE_TYPE_END_GROUP:
+ throw new RuntimeException("groups not supported at index " + tagPos);
+ case WIRE_TYPE_FIXED32:
+ encodedSize += 4;
+ mBuffer.skipRead(4);
+ break;
+ default:
+ throw new ProtoParseException("editEncodedSize Bad tag tag=0x"
+ + Integer.toHexString(tag) + " wireType=" + wireType
+ + " -- " + mBuffer.getDebugString());
+ }
+ }
+
+ return encodedSize;
+ }
+
+ /**
+ * Second compaction pass. Iterate through the data, and copy the data
+ * forward in the buffer, converting the pairs of uint32s into a single
+ * unsigned varint of the size.
+ */
+ private void compactSizes(int rawSize) {
+ int objectStart = mBuffer.getReadPos();
+ int objectEnd = objectStart + rawSize;
+ int tagPos;
+ while ((tagPos = mBuffer.getReadPos()) < objectEnd) {
+ int tag = readRawTag();
+
+ // For all the non-length-delimited field types, just skip over them,
+ // and we'll just System.arraycopy it later, either in the case for
+ // WIRE_TYPE_LENGTH_DELIMITED or at the top of the stack in compactIfNecessary().
+ final int wireType = tag & WIRE_TYPE_MASK;
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ while ((mBuffer.readRawByte() & 0x80) != 0) { }
+ break;
+ case WIRE_TYPE_FIXED64:
+ mBuffer.skipRead(8);
+ break;
+ case WIRE_TYPE_LENGTH_DELIMITED: {
+ // Copy everything up to now, including the tag for this field.
+ mBuffer.writeFromThisBuffer(mCopyBegin, mBuffer.getReadPos() - mCopyBegin);
+ // Write the new size.
+ final int childRawSize = mBuffer.readRawFixed32();
+ final int childEncodedSize = mBuffer.readRawFixed32();
+ mBuffer.writeRawVarint32(childEncodedSize);
+ // Next time, start copying from here.
+ mCopyBegin = mBuffer.getReadPos();
+ if (childRawSize >= 0) {
+ // This is raw data, not an object. Skip ahead by the size.
+ // Recurse into the child
+ mBuffer.skipRead(childEncodedSize);
+ } else {
+ compactSizes(-childRawSize);
+ }
+ break;
+ // TODO: What does regular proto do if the object would be 0 size
+ // (e.g. if it is all default values).
+ }
+ case WIRE_TYPE_START_GROUP:
+ case WIRE_TYPE_END_GROUP:
+ throw new RuntimeException("groups not supported at index " + tagPos);
+ case WIRE_TYPE_FIXED32:
+ mBuffer.skipRead(4);
+ break;
+ default:
+ throw new ProtoParseException("compactSizes Bad tag tag=0x"
+ + Integer.toHexString(tag) + " wireType=" + wireType
+ + " -- " + mBuffer.getDebugString());
+ }
+ }
+ }
+
+ /**
+ * Read a raw tag from the buffer.
+ */
+ private int readRawTag() {
+ if (mBuffer.getReadPos() == mBuffer.getReadableSize()) {
+ return 0;
+ }
+ return (int)mBuffer.readRawUnsigned();
+ }
+
+ /**
+ * Dump debugging data about the buffers with the given log tag.
+ */
+ public void dump(String tag) {
+ Log.d(tag, mBuffer.getDebugString());
+ mBuffer.dumpBuffers(tag);
+ }
+}
diff --git a/core/java/android/util/proto/ProtoParseException.java b/core/java/android/util/proto/ProtoParseException.java
new file mode 100644
index 0000000..5ba9bf8
--- /dev/null
+++ b/core/java/android/util/proto/ProtoParseException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.util.proto;
+
+import android.annotation.TestApi;
+
+/**
+ * Thrown when there is an error parsing protobuf data.
+ *
+ * @hide
+ */
+@TestApi
+public class ProtoParseException extends RuntimeException {
+
+ /**
+ * Construct a ProtoParseException.
+ *
+ * @param msg The message.
+ */
+ public ProtoParseException(String msg) {
+ super(msg);
+ }
+}
+
diff --git a/core/java/android/util/proto/package.html b/core/java/android/util/proto/package.html
new file mode 100644
index 0000000..a636bd4
--- /dev/null
+++ b/core/java/android/util/proto/package.html
@@ -0,0 +1,5 @@
+<body>
+Provides utility classes to export protocol buffers from the system.
+
+{@hide}
+</body>
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 85a4bf9..3beb00f 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,11 +16,11 @@
package android.view;
+import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
+
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
-import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -36,8 +36,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
-
/**
* Provides information about the size and density of a logical display.
* <p>
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index dd86062..790029b 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -26,18 +26,20 @@
public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments();
private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- private Configuration mConfiguration = Configuration.EMPTY;
+ private Configuration mConfiguration;
public DisplayAdjustments() {
}
public DisplayAdjustments(Configuration configuration) {
- mConfiguration = configuration;
+ mConfiguration = new Configuration(configuration != null
+ ? configuration : Configuration.EMPTY);
}
public DisplayAdjustments(DisplayAdjustments daj) {
setCompatibilityInfo(daj.mCompatInfo);
- mConfiguration = daj.mConfiguration;
+ mConfiguration = new Configuration(daj.mConfiguration != null
+ ? daj.mConfiguration : Configuration.EMPTY);
}
public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
@@ -62,7 +64,7 @@
throw new IllegalArgumentException(
"setConfiguration: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
}
- mConfiguration = configuration != null ? configuration : Configuration.EMPTY;
+ mConfiguration.setTo(configuration != null ? configuration : Configuration.EMPTY);
}
public Configuration getConfiguration() {
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 5a9a1ea..67cdfc5 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -16,12 +16,13 @@
package android.view;
-import dalvik.system.CloseGuard;
-
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
+import dalvik.annotation.optimization.FastNative;
+import dalvik.system.CloseGuard;
+
import java.lang.ref.WeakReference;
/**
@@ -47,6 +48,7 @@
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
MessageQueue messageQueue);
private static native void nativeDispose(long receiverPtr);
+ @FastNative
private static native void nativeScheduleVsync(long receiverPtr);
/**
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index bc40849..1aef6ec 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -23,10 +23,10 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
-import java.util.Arrays;
-
import libcore.util.Objects;
+import java.util.Arrays;
+
/**
* Describes the characteristics of a particular logical display.
* @hide
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 44d7a86..106f172 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -19,11 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.util.Pools.SynchronizedPool;
+import dalvik.annotation.optimization.FastNative;
+
/**
* A Canvas implementation that records view system drawing operations for deferred rendering.
* This is intended for use with a DisplayList. This class keeps a list of all the Paint and
@@ -32,7 +33,7 @@
*
* @hide
*/
-public class DisplayListCanvas extends Canvas {
+public final class DisplayListCanvas extends RecordingCanvas {
// The recording canvas pool should be large enough to handle a deeply nested
// view hierarchy because display lists are generated recursively.
private static final int POOL_LIMIT = 25;
@@ -40,7 +41,7 @@
private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
private static final SynchronizedPool<DisplayListCanvas> sPool =
- new SynchronizedPool<DisplayListCanvas>(POOL_LIMIT);
+ new SynchronizedPool<>(POOL_LIMIT);
RenderNode mNode;
private int mWidth;
@@ -50,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;
@@ -78,14 +80,11 @@
// 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
}
- private static native long nCreateDisplayListCanvas(int width, int height);
- private static native void nResetDisplayListCanvas(long canvas, int width, int height);
-
///////////////////////////////////////////////////////////////////////////
// Canvas management
///////////////////////////////////////////////////////////////////////////
@@ -131,9 +130,6 @@
return nGetMaximumTextureHeight();
}
- private static native int nGetMaximumTextureWidth();
- private static native int nGetMaximumTextureHeight();
-
///////////////////////////////////////////////////////////////////////////
// Setup
///////////////////////////////////////////////////////////////////////////
@@ -148,8 +144,6 @@
nInsertReorderBarrier(mNativeCanvasWrapper, false);
}
- private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
-
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
@@ -180,15 +174,10 @@
nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunctor, releasedCallback);
}
- private static native void nCallDrawGLFunction(long renderer,
- long drawGLFunction, Runnable releasedCallback);
-
///////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////
- protected static native long nFinishRecording(long renderer);
-
/**
* Draws the specified display list onto this canvas. The display list can only
* be drawn if {@link android.view.RenderNode#isValid()} returns true.
@@ -199,8 +188,6 @@
nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList());
}
- private static native void nDrawRenderNode(long renderer, long renderNode);
-
///////////////////////////////////////////////////////////////////////////
// Hardware layer
///////////////////////////////////////////////////////////////////////////
@@ -214,8 +201,6 @@
nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle());
}
- private static native void nDrawLayer(long renderer, long layer);
-
///////////////////////////////////////////////////////////////////////////
// Drawing
///////////////////////////////////////////////////////////////////////////
@@ -226,9 +211,6 @@
radius.getNativeContainer(), paint.getNativeContainer());
}
- private static native void nDrawCircle(long renderer, long propCx,
- long propCy, long propRadius, long propPaint);
-
public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
CanvasProperty<Float> ry, CanvasProperty<Paint> paint) {
@@ -238,9 +220,6 @@
paint.getNativeContainer());
}
- private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
- long propRight, long propBottom, long propRx, long propRy, long propPaint);
-
@Override
protected void throwIfCannotDraw(Bitmap bitmap) {
super.throwIfCannotDraw(bitmap);
@@ -250,4 +229,31 @@
"Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
}
}
+
+ @FastNative
+ private static native long nCreateDisplayListCanvas(long node, int width, int height);
+ @FastNative
+ private static native void nResetDisplayListCanvas(long canvas, long node,
+ int width, int height);
+ @FastNative
+ private static native int nGetMaximumTextureWidth();
+ @FastNative
+ private static native int nGetMaximumTextureHeight();
+ @FastNative
+ private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
+ @FastNative
+ private static native void nCallDrawGLFunction(long renderer,
+ long drawGLFunction, Runnable releasedCallback);
+ @FastNative
+ private static native long nFinishRecording(long renderer);
+ @FastNative
+ private static native void nDrawRenderNode(long renderer, long renderNode);
+ @FastNative
+ private static native void nDrawLayer(long renderer, long layer);
+ @FastNative
+ private static native void nDrawCircle(long renderer, long propCx,
+ long propCy, long propRadius, long propPaint);
+ @FastNative
+ private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
+ long propRight, long propBottom, long propRx, long propRy, long propPaint);
}
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 2baa0b4..16f2d7d 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -26,7 +26,7 @@
//TODO: Improve Javadoc
/**
* Represents an event that is sent out by the system at various times during a drag and drop
- * operation. It is a complex data structure that contains several important pieces of data about
+ * operation. It is a data structure that contains several important pieces of data about
* the operation and the underlying data.
* <p>
* View objects that receive a DragEvent call {@link #getAction()}, which returns
@@ -161,8 +161,8 @@
* event when they are added or becoming visible.
* </p>
* <p>
- * A View only receives further drag events if it returns {@code true} in response to
- * ACTION_DRAG_STARTED.
+ * A View only receives further drag events for the drag operation if it returns {@code true}
+ * in response to ACTION_DRAG_STARTED.
* </p>
* @see #ACTION_DRAG_ENDED
* @see #getX()
@@ -172,8 +172,9 @@
/**
* Action constant returned by {@link #getAction()}: Sent to a View after
- * {@link #ACTION_DRAG_ENTERED} if the drag shadow is still within the View object's bounding
- * box. The {@link #getX()} and {@link #getY()} methods supply
+ * {@link #ACTION_DRAG_ENTERED} while the drag shadow is still within the View object's bounding
+ * box, but not within a descendant view that can accept the data. The {@link #getX()} and
+ * {@link #getY()} methods supply
* the X and Y position of of the drag point within the View object's bounding box.
* <p>
* A View receives an {@link #ACTION_DRAG_ENTERED} event before receiving any
@@ -355,9 +356,10 @@
/**
* Returns the {@link android.content.ClipData} object sent to the system as part of the call
* to
- * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
+ * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
* This method only returns valid data if the event action is {@link #ACTION_DROP}.
- * @return The ClipData sent to the system by startDrag().
+ * @return The ClipData sent to the system by startDragAndDrop().
*/
public ClipData getClipData() {
return mClipData;
@@ -366,12 +368,14 @@
/**
* Returns the {@link android.content.ClipDescription} object contained in the
* {@link android.content.ClipData} object sent to the system as part of the call to
- * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
+ * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
* The drag handler or listener for a View can use the metadata in this object to decide if the
* View can accept the dragged View object's data.
* <p>
* This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
- * @return The ClipDescription that was part of the ClipData sent to the system by startDrag().
+ * @return The ClipDescription that was part of the ClipData sent to the system by
+ * startDragAndDrop().
*/
public ClipDescription getClipDescription() {
return mClipDescription;
@@ -384,7 +388,8 @@
/**
* Returns the local state object sent to the system as part of the call to
- * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
+ * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
* The object is intended to provide local information about the drag and drop operation. For
* example, it can indicate whether the drag and drop operation is a copy or a move.
* <p>
@@ -394,7 +399,7 @@
* <p>
* This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
* </p>
- * @return The local state object sent to the system by startDrag().
+ * @return The local state object sent to the system by startDragAndDrop().
*/
public Object getLocalState() {
return mLocalState;
diff --git a/core/java/android/view/FallbackEventHandler.java b/core/java/android/view/FallbackEventHandler.java
index dd68d89..8e00d6d 100644
--- a/core/java/android/view/FallbackEventHandler.java
+++ b/core/java/android/view/FallbackEventHandler.java
@@ -24,4 +24,3 @@
public void preDispatchKeyEvent(KeyEvent event);
public boolean dispatchKeyEvent(KeyEvent event);
}
-
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 2b938d0..800a63f 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -17,7 +17,6 @@
package android.view;
import android.annotation.IntDef;
-import android.view.Window;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/android/view/FrameMetricsObserver.java b/core/java/android/view/FrameMetricsObserver.java
index 6331357..9e81de0 100644
--- a/core/java/android/view/FrameMetricsObserver.java
+++ b/core/java/android/view/FrameMetricsObserver.java
@@ -17,15 +17,12 @@
package android.view;
import android.annotation.NonNull;
-import android.util.Log;
import android.os.Looper;
import android.os.MessageQueue;
import com.android.internal.util.VirtualRefBasePtr;
-import java.lang.NullPointerException;
import java.lang.ref.WeakReference;
-import java.lang.SuppressWarnings;
/**
* Provides streaming access to frame stats information from the rendering
diff --git a/core/java/android/view/HandlerActionQueue.java b/core/java/android/view/HandlerActionQueue.java
index 4758a34..d016a74 100644
--- a/core/java/android/view/HandlerActionQueue.java
+++ b/core/java/android/view/HandlerActionQueue.java
@@ -16,11 +16,9 @@
package android.view;
-import com.android.internal.util.GrowingArrayUtils;
-
import android.os.Handler;
-import java.util.ArrayList;
+import com.android.internal.util.GrowingArrayUtils;
/**
* Class used to enqueue pending work from Views when no Handler is attached.
diff --git a/core/java/android/view/IAssetAtlas.aidl b/core/java/android/view/IAssetAtlas.aidl
deleted file mode 100644
index edce059..0000000
--- a/core/java/android/view/IAssetAtlas.aidl
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Copyright (c) 2013, 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.view;
-
-import android.view.GraphicBuffer;
-
-/**
- * Programming interface to the system assets atlas. This atlas, when
- * present, holds preloaded drawable in a single, shareable graphics
- * buffer. This allows multiple processes to share the same data to
- * save up on memory.
- *
- * @hide
- */
-interface IAssetAtlas {
- /**
- * Indicates whether the atlas is compatible with the specified
- * parent process id. If the atlas' ppid does not match, this
- * method will return false.
- */
- boolean isCompatible(int ppid);
-
- /**
- * Returns the atlas buffer (texture) or null if the atlas is
- * not available yet.
- */
- GraphicBuffer getBuffer();
-
- /**
- * Returns the map of the bitmaps stored in the atlas or null
- * if the atlas is not available yet.
- *
- * Each bitmap is represented by several entries in the array:
- * long0: SkBitmap*, the native bitmap object
- * long1: x position
- * long2: y position
- * long3: rotated, 1 if the bitmap must be rotated, 0 otherwise
- */
- long[] getMap();
-}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 897e29b..e42b42a 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);
@@ -444,7 +444,7 @@
/**
* Retrieves the current stable insets from the primary display.
*/
- void getStableInsets(out Rect outInsets);
+ void getStableInsets(int displayId, out Rect outInsets);
/**
* Register shortcut key. Shortcut code is packed as:
@@ -454,12 +454,13 @@
void registerShortcutKey(in long shortcutCode, IShortcutService keySubscriber);
/**
- * Create the input consumer for wallpaper events.
+ * Create an input consumer by name.
*/
- void createWallpaperInputConsumer(out InputChannel inputChannel);
+ void createInputConsumer(String name, out InputChannel inputChannel);
/**
- * Remove the input consumer for wallpaper events.
+ * Destroy an input consumer by name. This method will also dispose the input channels
+ * associated with that InputConsumer.
*/
- void removeWallpaperInputConsumer();
+ boolean destroyInputConsumer(String name);
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f8796c3..55f64d9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -19,10 +19,10 @@
import android.content.Context;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
+import android.os.NullVibrator;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Vibrator;
-import android.os.NullVibrator;
import java.util.ArrayList;
import java.util.List;
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 91ef50d..20ab539 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -16,13 +16,13 @@
package android.view;
-import dalvik.system.CloseGuard;
-
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import android.util.SparseIntArray;
+import dalvik.system.CloseGuard;
+
import java.lang.ref.WeakReference;
/**
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index 304ea3f..b25fb65 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -16,12 +16,12 @@
package android.view;
-import dalvik.system.CloseGuard;
-
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
+import dalvik.system.CloseGuard;
+
import java.lang.ref.WeakReference;
/**
diff --git a/core/java/android/view/InputFilter.java b/core/java/android/view/InputFilter.java
index 4aba30c..d0dab40 100644
--- a/core/java/android/view/InputFilter.java
+++ b/core/java/android/view/InputFilter.java
@@ -20,12 +20,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.view.IInputFilter;
-import android.view.InputEvent;
-import android.view.InputEventConsistencyVerifier;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
/**
* Filters input events before they are dispatched to the system.
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index aebc601..582ae79 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -16,13 +16,13 @@
package android.view;
-import dalvik.system.CloseGuard;
-
import android.os.Looper;
import android.os.MessageQueue;
+import android.util.LongSparseArray;
import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
-import android.util.LongSparseArray;
+
+import dalvik.system.CloseGuard;
import java.lang.ref.WeakReference;
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index c8b89e7..88f2d34 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,14 +16,13 @@
package android.view;
+import android.hardware.input.InputManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.method.MetaKeyKeyListener;
import android.util.AndroidRuntimeException;
import android.util.SparseIntArray;
-import android.hardware.input.InputManager;
-import java.lang.Character;
import java.text.Normalizer;
/**
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index b73acda..5d8f336 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -21,7 +21,6 @@
import android.text.method.MetaKeyKeyListener;
import android.util.Log;
import android.util.SparseIntArray;
-import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.KeyData;
/**
diff --git a/core/java/android/view/KeyboardShortcutGroup.java b/core/java/android/view/KeyboardShortcutGroup.java
index 57d07c0..78f0b30 100644
--- a/core/java/android/view/KeyboardShortcutGroup.java
+++ b/core/java/android/view/KeyboardShortcutGroup.java
@@ -15,6 +15,8 @@
*/
package android.view;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
@@ -24,8 +26,6 @@
import java.util.Collections;
import java.util.List;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
/**
* A group of {@link KeyboardShortcutInfo}.
*/
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index a9f2699..c934a4e 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -15,14 +15,15 @@
*/
package android.view;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import static java.lang.Character.MIN_VALUE;
+
import android.annotation.Nullable;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
-import static com.android.internal.util.Preconditions.checkArgument;
-import static java.lang.Character.MIN_VALUE;
-
/**
* Information about a Keyboard Shortcut.
*/
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 914bd56..e3ac40c 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -16,11 +16,6 @@
package android.view;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.LayoutRes;
import android.annotation.Nullable;
import android.content.Context;
@@ -37,6 +32,11 @@
import android.util.Xml;
import android.widget.FrameLayout;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashMap;
@@ -51,20 +51,20 @@
*
* <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
* (Context.LAYOUT_INFLATER_SERVICE);</pre>
- *
+ *
* <p>
* To create a new LayoutInflater with an additional {@link Factory} for your
* own views, you can use {@link #cloneInContext} to clone an existing
* ViewFactory, and then call {@link #setFactory} on it to include your
* Factory.
- *
+ *
* <p>
* For performance reasons, view inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource
* (R.<em>something</em> file.)
- *
+ *
* @see Context#getSystemService
*/
public abstract class LayoutInflater {
@@ -95,7 +95,7 @@
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();
-
+
private HashMap<String, Boolean> mFilterMap;
private TypedValue mTempValue;
@@ -114,36 +114,36 @@
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
* to be inflated.
- *
+ *
*/
public interface Filter {
/**
- * Hook to allow clients of the LayoutInflater to restrict the set of Views
+ * Hook to allow clients of the LayoutInflater to restrict the set of Views
* that are allowed to be inflated.
- *
+ *
* @param clazz The class object for the View that is about to be inflated
- *
+ *
* @return True if this class is allowed to be inflated, or false otherwise
*/
@SuppressWarnings("unchecked")
boolean onLoadClass(Class clazz);
}
-
+
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
- *
+ *
* <p>
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
- *
+ *
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
- *
+ *
* @return View Newly created view. Return null for the default
* behavior.
*/
@@ -171,14 +171,14 @@
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
-
+
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
-
+
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
@@ -193,13 +193,13 @@
: mF2.onCreateView(name, context, attrs);
}
}
-
+
/**
* Create a new LayoutInflater instance associated with a particular Context.
* Applications will almost always want to use
* {@link Context#getSystemService Context.getSystemService()} to retrieve
* the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
- *
+ *
* @param context The Context in which this LayoutInflater will create its
* Views; most importantly, this supplies the theme from which the default
* values for their attributes are retrieved.
@@ -212,7 +212,7 @@
* Create a new LayoutInflater instance that is a copy of an existing
* LayoutInflater, optionally with its Context changed. For use in
* implementing {@link #cloneInContext}.
- *
+ *
* @param original The original LayoutInflater to copy.
* @param newContext The new Context to use.
*/
@@ -223,7 +223,7 @@
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
}
-
+
/**
* Obtains the LayoutInflater from the given context.
*/
@@ -241,15 +241,15 @@
* pointing to a different Context than the original. This is used by
* {@link ContextThemeWrapper} to create a new LayoutInflater to go along
* with the new Context theme.
- *
+ *
* @param newContext The new Context to associate with the new LayoutInflater.
* May be the same as the original Context if desired.
- *
+ *
* @return Returns a brand spanking new LayoutInflater object associated with
* the given Context.
*/
public abstract LayoutInflater cloneInContext(Context newContext);
-
+
/**
* Return the context we are running in, for access to resources, class
* loader, etc.
@@ -285,7 +285,7 @@
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
- *
+ *
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
@@ -345,13 +345,13 @@
public Filter getFilter() {
return mFilter;
}
-
+
/**
* Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
* which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
* throw an {@link InflateException}. This filter will replace any previous filter set on this
* LayoutInflater.
- *
+ *
* @param filter The Filter which restricts the set of Views that are allowed to be inflated.
* This filter will replace any previous filter set on this LayoutInflater.
*/
@@ -365,7 +365,7 @@
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy.
@@ -385,7 +385,7 @@
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
- *
+ *
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy.
@@ -400,7 +400,7 @@
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
@@ -437,7 +437,7 @@
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
- *
+ *
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
@@ -475,7 +475,7 @@
}
final String name = parser.getName();
-
+
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
@@ -579,17 +579,17 @@
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
- *
+ *
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
- *
+ *
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
- *
+ *
* @return View The newly instantiated view, or null.
*/
public final View createView(String name, String prefix, AttributeSet attrs)
@@ -608,7 +608,7 @@
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
+
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
@@ -627,7 +627,7 @@
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
+
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
@@ -689,10 +689,10 @@
* given the xml element name. Override it to handle custom view objects. If
* you override this in your subclass be sure to call through to
* super.onCreateView(name) for names you do not recognize.
- *
+ *
* @param name The fully qualified class name of the View to be create.
* @param attrs An AttributeSet of attributes to apply to the View.
- *
+ *
* @return View The View created.
*/
protected View onCreateView(String name, AttributeSet attrs)
@@ -842,7 +842,7 @@
}
final String name = parser.getName();
-
+
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index 1c67ba7..73ea9ee 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -16,11 +16,6 @@
package android.view;
-import com.android.internal.view.menu.MenuItemImpl;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.MenuRes;
import android.app.Activity;
import android.content.Context;
@@ -31,6 +26,11 @@
import android.util.Log;
import android.util.Xml;
+import com.android.internal.view.menu.MenuItemImpl;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@@ -49,15 +49,15 @@
/** Menu tag name in XML. */
private static final String XML_MENU = "menu";
-
+
/** Group tag name in XML. */
private static final String XML_GROUP = "group";
-
+
/** Item tag name in XML. */
private static final String XML_ITEM = "item";
private static final int NO_ID = 0;
-
+
private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE;
@@ -71,7 +71,7 @@
/**
* Constructs a menu inflater.
- *
+ *
* @see Activity#getMenuInflater()
*/
public MenuInflater(Context context) {
@@ -96,7 +96,7 @@
/**
* Inflate a menu hierarchy from the specified XML resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param menuRes Resource ID for an XML layout resource to load (e.g.,
* <code>R.menu.main_activity</code>)
* @param menu The Menu to inflate into. The items and submenus will be
@@ -107,7 +107,7 @@
try {
parser = mContext.getResources().getLayout(menuRes);
AttributeSet attrs = Xml.asAttributeSet(parser);
-
+
parseMenu(parser, attrs, menu);
} catch (XmlPullParserException e) {
throw new InflateException("Error inflating menu XML", e);
@@ -140,12 +140,12 @@
eventType = parser.next();
break;
}
-
+
throw new RuntimeException("Expecting menu, got " + tagName);
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
-
+
boolean reachedEndOfMenu = false;
while (!reachedEndOfMenu) {
switch (eventType) {
@@ -153,7 +153,7 @@
if (lookingForEndOfUnknownTag) {
break;
}
-
+
tagName = parser.getName();
if (tagName.equals(XML_GROUP)) {
menuState.readGroup(attrs);
@@ -171,7 +171,7 @@
unknownTagName = tagName;
}
break;
-
+
case XmlPullParser.END_TAG:
tagName = parser.getName();
if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
@@ -194,11 +194,11 @@
reachedEndOfMenu = true;
}
break;
-
+
case XmlPullParser.END_DOCUMENT:
throw new RuntimeException("Unexpected end of document");
}
-
+
eventType = parser.next();
}
}
@@ -229,10 +229,10 @@
private static class InflatedOnMenuItemClickListener
implements MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
-
+
private Object mRealOwner;
private Method mMethod;
-
+
public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
mRealOwner = realOwner;
Class<?> c = realOwner.getClass();
@@ -246,7 +246,7 @@
throw ex;
}
}
-
+
public boolean onMenuItemClick(MenuItem item) {
try {
if (mMethod.getReturnType() == Boolean.TYPE) {
@@ -277,7 +277,7 @@
}
return owner;
}
-
+
/**
* State for the current menu.
* <p>
@@ -316,7 +316,7 @@
private boolean itemChecked;
private boolean itemVisible;
private boolean itemEnabled;
-
+
/**
* Sync to attrs.xml enum, values in MenuItem:
* - 0: never
@@ -331,7 +331,7 @@
private String itemActionProviderClassName;
private String itemListenerMethodName;
-
+
private ActionProvider itemActionProvider;
private static final int defaultGroupId = NO_ID;
@@ -342,13 +342,13 @@
private static final boolean defaultItemChecked = false;
private static final boolean defaultItemVisible = true;
private static final boolean defaultItemEnabled = true;
-
+
public MenuState(final Menu menu) {
this.menu = menu;
-
+
resetGroup();
}
-
+
public void resetGroup() {
groupId = defaultGroupId;
groupCategory = defaultItemCategory;
@@ -364,7 +364,7 @@
public void readGroup(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.MenuGroup);
-
+
groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
@@ -374,7 +374,7 @@
a.recycle();
}
-
+
/**
* Called when the parser is pointing to an item tag.
*/
@@ -436,7 +436,7 @@
return shortcutString.charAt(0);
}
}
-
+
private void setItem(MenuItem item) {
item.setChecked(itemChecked)
.setVisible(itemVisible)
@@ -446,11 +446,11 @@
.setIcon(itemIconResId)
.setAlphabeticShortcut(itemAlphabeticShortcut)
.setNumericShortcut(itemNumericShortcut);
-
+
if (itemShowAsAction >= 0) {
item.setShowAsAction(itemShowAsAction);
}
-
+
if (itemListenerMethodName != null) {
if (mContext.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot "
@@ -494,14 +494,14 @@
setItem(item);
return item;
}
-
+
public SubMenu addSubMenuItem() {
itemAdded = true;
SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
setItem(subMenu.getItem());
return subMenu;
}
-
+
public boolean hasAddedItem() {
return itemAdded;
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index fab5364..2316b38 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -23,6 +23,9 @@
import android.os.SystemClock;
import android.util.SparseArray;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
/**
* Object used to report movement (mouse, pen, finger, trackball) events.
* Motion events may hold either absolute or relative movements and other data,
@@ -433,6 +436,14 @@
public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
/**
+ * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+ * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+ * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+ * @hide
+ */
+ public static final int FLAG_HOVER_EXIT_PENDING = 0x4;
+
+ /**
* Private flag that indicates when the system has detected that this motion event
* may be inconsistent with respect to the sequence of previously delivered motion events,
* such as when a pointer move event is sent but the pointer is not down.
@@ -1445,60 +1456,98 @@
float xOffset, float yOffset, float xPrecision, float yPrecision,
long downTimeNanos, long eventTimeNanos,
int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
- private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
- boolean keepHistory);
private static native void nativeDispose(long nativePtr);
private static native void nativeAddBatch(long nativePtr, long eventTimeNanos,
PointerCoords[] pointerCoords, int metaState);
-
- private static native int nativeGetDeviceId(long nativePtr);
- private static native int nativeGetSource(long nativePtr);
- private static native int nativeSetSource(long nativePtr, int source);
- private static native int nativeGetAction(long nativePtr);
- private static native void nativeSetAction(long nativePtr, int action);
- private static native boolean nativeIsTouchEvent(long nativePtr);
- private static native int nativeGetFlags(long nativePtr);
- private static native void nativeSetFlags(long nativePtr, int flags);
- private static native int nativeGetEdgeFlags(long nativePtr);
- private static native void nativeSetEdgeFlags(long nativePtr, int action);
- private static native int nativeGetMetaState(long nativePtr);
- private static native int nativeGetButtonState(long nativePtr);
- private static native void nativeSetButtonState(long nativePtr, int buttonState);
- private static native int nativeGetActionButton(long nativePtr);
- private static native void nativeSetActionButton(long nativePtr, int actionButton);
- private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
- private static native float nativeGetXOffset(long nativePtr);
- private static native float nativeGetYOffset(long nativePtr);
- private static native float nativeGetXPrecision(long nativePtr);
- private static native float nativeGetYPrecision(long nativePtr);
- private static native long nativeGetDownTimeNanos(long nativePtr);
- private static native void nativeSetDownTimeNanos(long nativePtr, long downTime);
-
- private static native int nativeGetPointerCount(long nativePtr);
- private static native int nativeGetPointerId(long nativePtr, int pointerIndex);
- private static native int nativeGetToolType(long nativePtr, int pointerIndex);
- private static native int nativeFindPointerIndex(long nativePtr, int pointerId);
-
- private static native int nativeGetHistorySize(long nativePtr);
- private static native long nativeGetEventTimeNanos(long nativePtr, int historyPos);
- private static native float nativeGetRawAxisValue(long nativePtr,
- int axis, int pointerIndex, int historyPos);
- private static native float nativeGetAxisValue(long nativePtr,
- int axis, int pointerIndex, int historyPos);
private static native void nativeGetPointerCoords(long nativePtr,
int pointerIndex, int historyPos, PointerCoords outPointerCoords);
private static native void nativeGetPointerProperties(long nativePtr,
int pointerIndex, PointerProperties outPointerProperties);
- private static native void nativeScale(long nativePtr, float scale);
- private static native void nativeTransform(long nativePtr, Matrix matrix);
-
private static native long nativeReadFromParcel(long nativePtr, Parcel parcel);
private static native void nativeWriteToParcel(long nativePtr, Parcel parcel);
private static native String nativeAxisToString(int axis);
private static native int nativeAxisFromString(String label);
+ // -------------- @FastNative -------------------------
+
+ @FastNative
+ private static native int nativeGetPointerId(long nativePtr, int pointerIndex);
+ @FastNative
+ private static native int nativeGetToolType(long nativePtr, int pointerIndex);
+ @FastNative
+ private static native long nativeGetEventTimeNanos(long nativePtr, int historyPos);
+ @FastNative
+ private static native float nativeGetRawAxisValue(long nativePtr,
+ int axis, int pointerIndex, int historyPos);
+ @FastNative
+ private static native float nativeGetAxisValue(long nativePtr,
+ int axis, int pointerIndex, int historyPos);
+
+ // -------------- @CriticalNative ----------------------
+
+ @CriticalNative
+ private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
+ boolean keepHistory);
+ @CriticalNative
+ private static native int nativeGetDeviceId(long nativePtr);
+ @CriticalNative
+ private static native int nativeGetSource(long nativePtr);
+ @CriticalNative
+ private static native int nativeSetSource(long nativePtr, int source);
+ @CriticalNative
+ private static native int nativeGetAction(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetAction(long nativePtr, int action);
+ @CriticalNative
+ private static native boolean nativeIsTouchEvent(long nativePtr);
+ @CriticalNative
+ private static native int nativeGetFlags(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetFlags(long nativePtr, int flags);
+ @CriticalNative
+ private static native int nativeGetEdgeFlags(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetEdgeFlags(long nativePtr, int action);
+ @CriticalNative
+ private static native int nativeGetMetaState(long nativePtr);
+ @CriticalNative
+ private static native int nativeGetButtonState(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetButtonState(long nativePtr, int buttonState);
+ @CriticalNative
+ private static native int nativeGetActionButton(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetActionButton(long nativePtr, int actionButton);
+ @CriticalNative
+ private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
+ @CriticalNative
+ private static native float nativeGetXOffset(long nativePtr);
+ @CriticalNative
+ private static native float nativeGetYOffset(long nativePtr);
+ @CriticalNative
+ private static native float nativeGetXPrecision(long nativePtr);
+ @CriticalNative
+ private static native float nativeGetYPrecision(long nativePtr);
+ @CriticalNative
+ private static native long nativeGetDownTimeNanos(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetDownTimeNanos(long nativePtr, long downTime);
+
+ @CriticalNative
+ private static native int nativeGetPointerCount(long nativePtr);
+ @CriticalNative
+ private static native int nativeFindPointerIndex(long nativePtr, int pointerId);
+
+ @CriticalNative
+ private static native int nativeGetHistorySize(long nativePtr);
+
+ @CriticalNative
+ private static native void nativeScale(long nativePtr, float scale);
+ @CriticalNative
+ private static native void nativeTransform(long nativePtr, long matrix);
+
private MotionEvent() {
}
@@ -1906,6 +1955,20 @@
: flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
}
+ /** @hide */
+ public final boolean isHoverExitPending() {
+ final int flags = getFlags();
+ return (flags & FLAG_HOVER_EXIT_PENDING) != 0;
+ }
+
+ /** @hide */
+ public void setHoverExitPending(boolean hoverExitPending) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, hoverExitPending
+ ? flags | FLAG_HOVER_EXIT_PENDING
+ : flags & ~FLAG_HOVER_EXIT_PENDING);
+ }
+
/**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
@@ -2065,7 +2128,7 @@
public final int getPointerCount() {
return nativeGetPointerCount(mNativePtr);
}
-
+
/**
* Return the pointer identifier associated with a particular pointer
* data index in this event. The identifier tells you the actual pointer
@@ -2891,7 +2954,7 @@
throw new IllegalArgumentException("matrix must not be null");
}
- nativeTransform(mNativePtr, matrix);
+ nativeTransform(mNativePtr, matrix.native_instance);
}
/**
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index fc79f53..998fd01 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -17,9 +17,6 @@
package android.view;
import android.annotation.NonNull;
-import android.util.SparseArray;
-import com.android.internal.util.XmlUtils;
-
import android.annotation.XmlRes;
import android.content.Context;
import android.content.res.Resources;
@@ -32,6 +29,9 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.XmlUtils;
/**
* Represents an icon that can be used as a mouse pointer.
diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java
new file mode 100644
index 0000000..e4b91b7
--- /dev/null
+++ b/core/java/android/view/RecordingCanvas.java
@@ -0,0 +1,641 @@
+/*
+ * 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.view;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.graphics.BaseCanvas;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.NinePatch;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.TemporaryBuffer;
+import android.text.GraphicsOperations;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * This class is a base class for canvases that defer drawing operations, so all
+ * the draw operations can be marked @FastNative. It contains a re-implementation of
+ * all the methods in {@link BaseCanvas}.
+ *
+ * @hide
+ */
+public class RecordingCanvas extends Canvas {
+
+ public RecordingCanvas(long nativeCanvas) {
+ super(nativeCanvas);
+ }
+
+ @Override
+ public final void drawArc(float left, float top, float right, float bottom, float startAngle,
+ float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+ nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
+ useCenter, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle,
+ boolean useCenter, @NonNull Paint paint) {
+ drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
+ paint);
+ }
+
+ @Override
+ public final void drawARGB(int a, int r, int g, int b) {
+ drawColor(Color.argb(a, r, g, b));
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, float left, float top,
+ @Nullable Paint paint) {
+ throwIfCannotDraw(bitmap);
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top,
+ paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix,
+ @Nullable Paint paint) {
+ nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // check for valid input
+ if (width < 0) {
+ throw new IllegalArgumentException("width must be >= 0");
+ }
+ if (height < 0) {
+ throw new IllegalArgumentException("height must be >= 0");
+ }
+ if (Math.abs(stride) < width) {
+ throw new IllegalArgumentException("abs(stride) must be >= width");
+ }
+ int lastScanline = offset + (height - 1) * stride;
+ int length = colors.length;
+ if (offset < 0 || (offset + width > length) || lastScanline < 0
+ || (lastScanline + width > length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // quick escape if there's nothing to draw
+ if (width == 0 || height == 0) {
+ return;
+ }
+ // punch down to native for the actual draw
+ nDrawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // call through to the common float version
+ drawBitmap(colors, offset, stride, (float) x, (float) y, width, height,
+ hasAlpha, paint);
+ }
+
+ @Override
+ public final void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+ @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+ @Nullable Paint paint) {
+ if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ if (meshWidth == 0 || meshHeight == 0) {
+ return;
+ }
+ int count = (meshWidth + 1) * (meshHeight + 1);
+ // we mul by 2 since we need two floats per vertex
+ checkRange(verts.length, vertOffset, count * 2);
+ if (colors != null) {
+ // no mul by 2, since we need only 1 color per vertex
+ checkRange(colors.length, colorOffset, count);
+ }
+ nDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
+ verts, vertOffset, colors, colorOffset,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Override
+ public final void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color) {
+ nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
+ }
+
+ @Override
+ public final void drawLine(float startX, float startY, float stopX, float stopY,
+ @NonNull Paint paint) {
+ nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
+ drawLines(pts, 0, pts.length, paint);
+ }
+
+ @Override
+ public final void drawOval(float left, float top, float right, float bottom,
+ @NonNull Paint paint) {
+ nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+ if (oval == null) {
+ throw new NullPointerException();
+ }
+ drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
+ }
+
+ @Override
+ public final void drawPaint(@NonNull Paint paint) {
+ nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ @Override
+ public final void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ @Override
+ public final void drawPath(@NonNull Path path, @NonNull Paint paint) {
+ if (path.isSimplePath && path.rects != null) {
+ nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
+ } else {
+ nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
+ }
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture) {
+ picture.endRecording();
+ int restoreCount = save();
+ picture.draw(this);
+ restoreToCount(restoreCount);
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture, @NonNull Rect 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());
+ }
+ drawPicture(picture);
+ restore();
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
+ }
+
+ @Override
+ public final void drawPoint(float x, float y, @NonNull Paint paint) {
+ nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
+ drawPoints(pts, 0, pts.length, paint);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawPosText(@NonNull char[] text, int index, int count,
+ @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length || count * 2 > pos.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = 0; i < count; i++) {
+ drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
+ }
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
+ }
+
+ @Override
+ public final void drawRect(float left, float top, float right, float bottom,
+ @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+ drawRect(r.left, r.top, r.right, r.bottom, paint);
+ }
+
+ @Override
+ public final void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper,
+ rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRGB(int r, int g, int b) {
+ drawColor(Color.rgb(r, g, b));
+ }
+
+ @Override
+ public final void drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, @NonNull Paint paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+ drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
+ }
+
+ @Override
+ public final void drawText(@NonNull char[] text, int index, int count, float x, float y,
+ @NonNull Paint paint) {
+ if ((index | count | (index + count)
+ | (text.length - index - count)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
+ paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ @Override
+ public final void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (text instanceof String || text instanceof SpannedString
+ || text instanceof SpannableString) {
+ nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawText(this, start, end, x, y,
+ paint);
+ } else {
+ char[] buf = TemporaryBuffer.obtain(end - start);
+ TextUtils.getChars(text, start, end, buf, 0);
+ nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ @Override
+ public final void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+ nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
+ paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ @Override
+ public final void drawText(@NonNull String text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
+ paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ @Override
+ public final void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+ float hOffset, float vOffset, @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
+ path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ @Override
+ public final void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+ float vOffset, @NonNull Paint paint) {
+ if (text.length() > 0) {
+ nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+ }
+
+ @Override
+ public final void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+ int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((index | count | contextIndex | contextCount | index - contextIndex
+ | (contextIndex + contextCount) - (index + count)
+ | text.length - (contextIndex + contextCount)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
+ x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ @Override
+ public final void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+ int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((start | end | contextStart | contextEnd | start - contextStart | end - start
+ | contextEnd - end | text.length() - contextEnd) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (text instanceof String || text instanceof SpannedString
+ || text instanceof SpannableString) {
+ nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
+ contextEnd, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawTextRun(this, start, end,
+ contextStart, contextEnd, x, y, isRtl, paint);
+ } else {
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ @Override
+ public final void drawVertices(@NonNull VertexMode mode, int vertexCount,
+ @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset,
+ @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset,
+ int indexCount, @NonNull Paint paint) {
+ checkRange(verts.length, vertOffset, vertexCount);
+ if (isHardwareAccelerated()) {
+ return;
+ }
+ if (texs != null) {
+ checkRange(texs.length, texOffset, vertexCount);
+ }
+ if (colors != null) {
+ checkRange(colors.length, colorOffset, vertexCount / 2);
+ }
+ if (indices != null) {
+ checkRange(indices.length, indexOffset, indexCount);
+ }
+ nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
+ vertOffset, texs, texOffset, colors, colorOffset,
+ indices, indexOffset, indexCount, paint.getNativeInstance());
+ }
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+ long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap,
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ long nativePaintOrZero, int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+ float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero);
+
+ @FastNative
+ private static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+ @FastNative
+ private static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+ @FastNative
+ private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+ @FastNative
+ private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ @FastNative
+ private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
+ float stopY, long nativePaint);
+
+ @FastNative
+ private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ @FastNative
+ private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ @FastNative
+ private static native void nDrawOval(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ @FastNative
+ private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius,
+ long nativePaint);
+
+ @FastNative
+ private static native void nDrawArc(long nativeCanvas, float left, float top, float right,
+ float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint);
+
+ @FastNative
+ private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
+ float bottom, float rx, float ry, long nativePaint);
+
+ @FastNative
+ private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+ @FastNative
+ private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+ @FastNative
+ private static native void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero,
+ int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmapMatrix(long nativeCanvas, Bitmap bitmap,
+ long nativeMatrix, long nativePaint);
+
+ @FastNative
+ private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, int meshWidth,
+ int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset,
+ long nativePaint);
+
+ @FastNative
+ private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
+ int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
+ short[] indices, int indexOffset, int indexCount, long nativePaint);
+
+ @FastNative
+ private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
+ float x, float y, int flags, long nativePaint, long nativeTypeface);
+
+ @FastNative
+ private static native void nDrawText(long nativeCanvas, String text, int start, int end,
+ float x, float y, int flags, long nativePaint, long nativeTypeface);
+
+ @FastNative
+ private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end,
+ int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint,
+ long nativeTypeface);
+
+ @FastNative
+ private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
+ int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
+ long nativeTypeface);
+
+ @FastNative
+ private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
+ long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint,
+ long nativeTypeface);
+
+ @FastNative
+ private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath,
+ float hOffset, float vOffset, int flags, long nativePaint, long nativeTypeface);
+}
diff --git a/core/java/android/view/RemotableViewMethod.java b/core/java/android/view/RemotableViewMethod.java
index e5cae84..03aed9a 100644
--- a/core/java/android/view/RemotableViewMethod.java
+++ b/core/java/android/view/RemotableViewMethod.java
@@ -36,6 +36,3 @@
*/
String asyncImpl() default "";
}
-
-
-
diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java
index ba592d29..e1ef059 100644
--- a/core/java/android/view/RenderNodeAnimatorSetHelper.java
+++ b/core/java/android/view/RenderNodeAnimatorSetHelper.java
@@ -16,6 +16,7 @@
package android.view;
import android.animation.TimeInterpolator;
+
import com.android.internal.view.animation.FallbackLUTInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java
index b77be8c..4c555ae 100644
--- a/core/java/android/view/RoundScrollbarRenderer.java
+++ b/core/java/android/view/RoundScrollbarRenderer.java
@@ -19,8 +19,8 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.RectF;
import android.graphics.Rect;
+import android.graphics.RectF;
/**
* Helper class for drawing round scroll bars on round Wear devices.
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 7cd161c..9787494 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -20,7 +20,6 @@
import android.content.res.Resources;
import android.os.Build;
import android.os.Handler;
-import android.os.SystemClock;
/**
* Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
diff --git a/core/java/android/view/SearchEvent.java b/core/java/android/view/SearchEvent.java
index 643cc3e..72b5e4b 100644
--- a/core/java/android/view/SearchEvent.java
+++ b/core/java/android/view/SearchEvent.java
@@ -16,8 +16,6 @@
package android.view;
-import android.view.InputDevice;
-
/**
* Class that contains information about an event that triggers a search.
*/
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 22e68a3..ecd5e3b 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -26,11 +26,11 @@
import android.os.Parcelable;
import android.util.Log;
+import dalvik.system.CloseGuard;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import dalvik.system.CloseGuard;
-
/**
* Handle onto a raw buffer that is being managed by the screen compositor.
*
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6456826..b87250e 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -16,7 +16,6 @@
package android.view;
-import dalvik.system.CloseGuard;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Region;
@@ -24,6 +23,8 @@
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
+import dalvik.system.CloseGuard;
+
/**
* SurfaceControl
* @hide
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 2a2f659..80f447e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,11 +16,9 @@
package android.view;
-import com.android.internal.view.BaseIWindow;
-
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
@@ -28,12 +26,14 @@
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.ParcelFileDescriptor;
import android.util.AttributeSet;
import android.util.Log;
+import com.android.internal.view.BaseIWindow;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
@@ -600,7 +600,9 @@
// surfaceDestroyed and surfaceCreated, we force a disconnect,
// so the next connect will always work if we end up reusing
// the surface.
- mSurface.forceScopedDisconnect();
+ if (mSurface.isValid()) {
+ mSurface.forceScopedDisconnect();
+ }
}
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index ce390a2..0bb84cc 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,9 +16,9 @@
package android.view;
-import android.app.ActivityManagerNative;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.ActivityManagerNative;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -916,7 +916,6 @@
mInitialized = true;
initSched(context, renderProxy);
initGraphicsStats(context, renderProxy);
- initAssetAtlas(context, renderProxy);
}
private static void initSched(Context context, long renderProxy) {
@@ -944,32 +943,6 @@
Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
}
}
-
- private static void initAssetAtlas(Context context, long renderProxy) {
- IBinder binder = ServiceManager.getService("assetatlas");
- if (binder == null) return;
-
- IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
- try {
- if (atlas.isCompatible(android.os.Process.myPpid())) {
- GraphicBuffer buffer = atlas.getBuffer();
- if (buffer != null) {
- long[] map = atlas.getMap();
- if (map != null) {
- nSetAtlas(renderProxy, buffer, map);
- }
- // If IAssetAtlas is not the same class as the IBinder
- // we are using a remote service and we can safely
- // destroy the graphic buffer
- if (atlas.getClass() != binder.getClass()) {
- buffer.destroy();
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Could not acquire atlas", e);
- }
- }
}
void addFrameMetricsObserver(FrameMetricsObserver observer) {
@@ -984,7 +957,6 @@
static native void setupShadersDiskCache(String cacheFile);
- private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
private static native int nGetRenderThreadTid(long nativeProxy);
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
index 27b49db..cf36f43 100644
--- a/core/java/android/view/TouchDelegate.java
+++ b/core/java/android/view/TouchDelegate.java
@@ -17,9 +17,6 @@
package android.view;
import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
/**
* Helper class to handle situations where you want a view to have a larger touch area than its
@@ -33,24 +30,24 @@
* </p>
*/
public class TouchDelegate {
-
+
/**
- * View that should receive forwarded touch events
+ * View that should receive forwarded touch events
*/
private View mDelegateView;
-
+
/**
* Bounds in local coordinates of the containing view that should be mapped to the delegate
* view. This rect is used for initial hit testing.
*/
private Rect mBounds;
-
+
/**
* mBounds inflated to include some slop. This rect is to track whether the motion events
* should be considered to be be within the delegate view.
*/
private Rect mSlopBounds;
-
+
/**
* True if the delegate had been targeted on a down event (intersected mBounds).
*/
@@ -82,7 +79,7 @@
/**
* Constructor
- *
+ *
* @param bounds Bounds in local coordinates of the containing view that should be mapped to
* the delegate view
* @param delegateView The view that should receive motion events
@@ -99,7 +96,7 @@
/**
* Will forward touch events to the delegate view if the event is within the bounds
* specified in the constructor.
- *
+ *
* @param event The touch event to forward
* @return True if the event was forwarded to the delegate, false otherwise.
*/
@@ -136,7 +133,7 @@
}
if (sendToDelegate) {
final View delegateView = mDelegateView;
-
+
if (hit) {
// Offset event coordinates to be inside the target view
event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d060aac..20876a9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,14 @@
package android.view;
+import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
+
+import static java.lang.Math.max;
+
import android.animation.AnimatorInflater;
import android.animation.StateListAnimator;
import android.annotation.CallSuper;
@@ -40,6 +48,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
@@ -79,11 +88,11 @@
import android.util.StateSet;
import android.util.SuperNotCalledException;
import android.util.TypedValue;
-import android.view.ContextMenu.ContextMenuInfo;
import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -99,17 +108,15 @@
import android.widget.Checkable;
import android.widget.FrameLayout;
import android.widget.ScrollBarDrawable;
-import static android.os.Build.VERSION_CODES.*;
-import static java.lang.Math.max;
import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
-import java.lang.NullPointerException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -757,6 +764,9 @@
AccessibilityEventSource {
private static final boolean DBG = false;
+ /** @hide */
+ public static boolean DEBUG_DRAW = false;
+
/**
* The logging tag used by this class with android.util.Log.
*/
@@ -1184,6 +1194,8 @@
*/
static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
+ private static Paint sDebugPaint;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -1655,6 +1667,10 @@
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
| AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+ static final int DEBUG_CORNERS_COLOR = Color.rgb(63, 127, 255);
+
+ static final int DEBUG_CORNERS_SIZE_DIP = 8;
+
/**
* Temporary Rect currently for use in setBackground(). This will probably
* be extended in the future to hold our own class with more than just
@@ -4743,6 +4759,10 @@
mRenderNode = RenderNode.create(getClass().getName(), this);
}
+ final boolean debugDraw() {
+ return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
+ }
+
private static SparseArray<String> getAttributeMap() {
if (mAttributeMap == null) {
mAttributeMap = new SparseArray<>();
@@ -16137,6 +16157,9 @@
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
+ if (debugDraw()) {
+ debugDrawFocus(canvas);
+ }
} else {
draw(canvas);
}
@@ -17115,6 +17138,44 @@
return more;
}
+ static Paint getDebugPaint() {
+ if (sDebugPaint == null) {
+ sDebugPaint = new Paint();
+ sDebugPaint.setAntiAlias(false);
+ }
+ return sDebugPaint;
+ }
+
+ final int dipsToPixels(int dips) {
+ float scale = getContext().getResources().getDisplayMetrics().density;
+ return (int) (dips * scale + 0.5f);
+ }
+
+ final private void debugDrawFocus(Canvas canvas) {
+ if (isFocused()) {
+ final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
+ final int l = mScrollX;
+ final int r = l + mRight - mLeft;
+ final int t = mScrollY;
+ final int b = t + mBottom - mTop;
+
+ final Paint paint = getDebugPaint();
+ paint.setColor(DEBUG_CORNERS_COLOR);
+
+ // Draw squares in corners.
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(l, t, l + cornerSquareSize, t + cornerSquareSize, paint);
+ canvas.drawRect(r - cornerSquareSize, t, r, t + cornerSquareSize, paint);
+ canvas.drawRect(l, b - cornerSquareSize, l + cornerSquareSize, b, paint);
+ canvas.drawRect(r - cornerSquareSize, b - cornerSquareSize, r, b, paint);
+
+ // Draw big X across the view.
+ paint.setStyle(Paint.Style.STROKE);
+ canvas.drawLine(l, t, r, b, paint);
+ canvas.drawLine(l, b, r, t, paint);
+ }
+ }
+
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
@@ -17169,6 +17230,10 @@
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
+ if (debugDraw()) {
+ debugDrawFocus(canvas);
+ }
+
// we're done...
return;
}
@@ -17317,6 +17382,10 @@
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
+
+ if (debugDraw()) {
+ debugDrawFocus(canvas);
+ }
}
/**
@@ -20661,7 +20730,9 @@
return false;
}
- data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+ if (data != null) {
+ data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+ }
boolean okay = false;
@@ -20830,6 +20901,9 @@
* {@link android.view.DragEvent#ACTION_DROP} if it consumed the drop, or
* {@code false} if it didn't.
* </p>
+ * <p>
+ * For all other events, the return value is ignored.
+ * </p>
*/
public boolean onDragEvent(DragEvent event) {
return false;
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index e1ff0d6..047a515 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -51,8 +51,8 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1b3f6ff..e39cb96 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -16,10 +16,13 @@
package android.view;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
import android.animation.LayoutTransition;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.UiThread;
+import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -61,8 +64,6 @@
import java.util.List;
import java.util.Map;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
/**
* <p>
* A <code>ViewGroup</code> is a special view that can contain other views
@@ -115,8 +116,6 @@
private static final String TAG = "ViewGroup";
private static final boolean DBG = false;
- /** @hide */
- public static boolean DEBUG_DRAW = false;
/**
* Views which have been hidden or removed which need to be animated on
@@ -475,7 +474,6 @@
private static final int ARRAY_INITIAL_CAPACITY = 12;
private static final int ARRAY_CAPACITY_INCREMENT = 12;
- private static Paint sDebugPaint;
private static float[] sDebugLines;
// Used to draw cached views
@@ -585,10 +583,6 @@
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
- private boolean debugDraw() {
- return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
- }
-
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
@@ -1380,6 +1374,7 @@
boolean retval = false;
final float tx = event.mX;
final float ty = event.mY;
+ final ClipData td = event.mClipData;
// Dispatch down the view hierarchy
final PointF localPoint = getLocalPoint();
@@ -1465,6 +1460,7 @@
// ACTION_DRAG_EXITED.
event.mX = 0;
event.mY = 0;
+ event.mClipData = null;
if (mCurrentDragChild != null) {
event.mAction = DragEvent.ACTION_DRAG_EXITED;
@@ -1479,6 +1475,7 @@
event.mAction = action;
event.mX = tx;
event.mY = ty;
+ event.mClipData = td;
}
mCurrentDragChild = target;
}
@@ -1861,8 +1858,11 @@
// Synthesize an exit from a move or enter.
// Ignore the result because hover focus has moved to a different view.
if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ final boolean hoverExitPending = event.isHoverExitPending();
+ event.setHoverExitPending(true);
dispatchTransformedGenericPointerEvent(
event, child); // move
+ event.setHoverExitPending(hoverExitPending);
}
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
@@ -1876,8 +1876,10 @@
firstOldHoverTarget = nextOldHoverTarget;
}
- // Send events to the view group itself if no children have handled it.
- boolean newHoveredSelf = !handled;
+ // Send events to the view group itself if no children have handled it and the view group
+ // itself is not currently being hover-exited.
+ boolean newHoveredSelf = !handled &&
+ (action != MotionEvent.ACTION_HOVER_EXIT) && !event.isHoverExitPending();
if (newHoveredSelf == mHoveredSelf) {
if (newHoveredSelf) {
// Send event to the view group as before.
@@ -3371,11 +3373,6 @@
fillRect(c, paint, x1, y1, x1 + lw * sign(dx), y1 + dy);
}
- private int dipsToPixels(int dips) {
- float scale = getContext().getResources().getDisplayMetrics().density;
- return (int) (dips * scale + 0.5f);
- }
-
private static void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint,
int lineLength, int lineWidth) {
drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth);
@@ -3444,10 +3441,10 @@
// Draw clip bounds
{
- paint.setColor(Color.rgb(63, 127, 255));
+ paint.setColor(DEBUG_CORNERS_COLOR);
paint.setStyle(Paint.Style.FILL);
- int lineLength = dipsToPixels(8);
+ int lineLength = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
int lineWidth = dipsToPixels(1);
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
@@ -7922,14 +7919,6 @@
}
}
- private static Paint getDebugPaint() {
- if (sDebugPaint == null) {
- sDebugPaint = new Paint();
- sDebugPaint.setAntiAlias(false);
- }
- return sDebugPaint;
- }
-
private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
if (sDebugLines== null) {
// TODO: This won't work with multiple UI threads in a single process
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index c604234..6c84b63 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -17,8 +17,9 @@
package android.view;
import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
@@ -42,7 +43,7 @@
* <p>This class is not constructed by the caller, but rather by the View whose properties
* it will animate. Calls to {@link android.view.View#animate()} will return a reference
* to the appropriate ViewPropertyAnimator object for that View.</p>
- *
+ *
*/
public class ViewPropertyAnimator {
@@ -332,7 +333,7 @@
* Sets the interpolator for the underlying animator that animates the requested properties.
* By default, the animator uses the default interpolator for ValueAnimator. Calling this method
* will cause the declared object to be used instead.
- *
+ *
* @param interpolator The TimeInterpolator to be used for ensuing property animations. A value
* of <code>null</code> will result in linear interpolation.
* @return This object, allowing calls to methods in this class to be chained.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ec29abf..87b330d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -29,6 +29,7 @@
import android.annotation.NonNull;
import android.app.ActivityManagerNative;
import android.app.ResourcesManager;
+import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentCallbacks;
import android.content.Context;
@@ -5551,8 +5552,8 @@
// Remember who the current drag target is pre-dispatch
final View prevDragView = mCurrentDragView;
- if (what == DragEvent.ACTION_DROP) {
- event.getClipData().prepareToEnterProcess();
+ if (what == DragEvent.ACTION_DROP && event.mClipData != null) {
+ event.mClipData.prepareToEnterProcess();
}
// Now dispatch the drag/drop event
@@ -5657,9 +5658,11 @@
final float tx = event.mX;
final float ty = event.mY;
final int action = event.mAction;
+ final ClipData td = event.mClipData;
// Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
event.mX = 0;
event.mY = 0;
+ event.mClipData = null;
if (mCurrentDragView != null) {
event.mAction = DragEvent.ACTION_DRAG_EXITED;
@@ -5674,6 +5677,7 @@
event.mAction = action;
event.mX = tx;
event.mY = ty;
+ event.mClipData = td;
}
mCurrentDragView = newDragTarget;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index b046e2d..2c13831 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
@@ -24,7 +26,6 @@
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.annotation.SystemApi;
-import android.app.ActivityManagerNative;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -44,8 +45,6 @@
import android.transition.TransitionManager;
import android.view.accessibility.AccessibilityEvent;
-import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-
import java.util.List;
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 395f738..f14acaa 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -64,6 +64,13 @@
/** @hide */
int DOCKED_BOTTOM = 4;
+ /** @hide */
+ final static String INPUT_CONSUMER_PIP = "pip_input_consumer";
+ /** @hide */
+ final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer";
+ /** @hide */
+ final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer";
+
/**
* Exception that is thrown when trying to add view whose
* {@link LayoutParams} {@link LayoutParams#token}
@@ -1571,30 +1578,30 @@
public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
/**
- * Value for {@link #rotationAnimation} to define the animation used to
- * specify that this window will rotate in or out following a rotation.
+ * Value for {@link #rotationAnimation} which specifies that this
+ * window will visually rotate in or out following a rotation.
*/
public static final int ROTATION_ANIMATION_ROTATE = 0;
/**
- * Value for {@link #rotationAnimation} to define the animation used to
- * specify that this window will fade in or out following a rotation.
+ * Value for {@link #rotationAnimation} which specifies that this
+ * window will fade in or out following a rotation.
*/
public static final int ROTATION_ANIMATION_CROSSFADE = 1;
/**
- * Value for {@link #rotationAnimation} to define the animation used to
- * specify that this window will immediately disappear or appear following
- * a rotation.
+ * Value for {@link #rotationAnimation} which specifies that this window
+ * will immediately disappear or appear following a rotation.
*/
public static final int ROTATION_ANIMATION_JUMPCUT = 2;
/**
* Value for {@link #rotationAnimation} to specify seamless rotation mode.
* This works like JUMPCUT but will fall back to CROSSFADE if rotation
- * can't be applied without pausing the screen.
- *
- * @hide
+ * can't be applied without pausing the screen. For example, this is ideal
+ * for Camera apps which don't want the viewfinder contents to ever rotate
+ * or fade (and rather to be seamless) but also don't want ROTATION_ANIMATION_JUMPCUT
+ * during app transition scenarios where seamless rotation can't be applied.
*/
public static final int ROTATION_ANIMATION_SEAMLESS = 3;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index dd4e096..c1b8f04 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -23,7 +23,6 @@
import android.os.RemoteException;
import com.android.internal.os.IResultReceiver;
-import com.android.internal.R;
import java.util.List;
@@ -38,11 +37,11 @@
* Additional window manager specific layout parameters are defined for
* control over how windows are displayed. It also implements the {@link WindowManager}
* interface, allowing you to control the displays attached to the device.
- *
+ *
* <p>Applications will not normally use WindowManager directly, instead relying
* on the higher-level facilities in {@link android.app.Activity} and
* {@link android.app.Dialog}.
- *
+ *
* <p>Even for low-level window manager access, it is almost never correct to use
* this class. For example, {@link android.app.Activity#getWindowManager}
* provides a window manager for adding windows that are associated with that
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index da361c1..a6be493 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -25,12 +25,12 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.view.animation.Animation;
+
import com.android.internal.policy.IShortcutService;
import java.io.PrintWriter;
@@ -458,7 +458,7 @@
/**
* Add a input consumer which will consume all input events going to any window below it.
*/
- public InputConsumer addInputConsumer(Looper looper,
+ public InputConsumer createInputConsumer(Looper looper, String name,
InputEventReceiver.Factory inputEventReceiverFactory);
/**
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 44f6fac..8084195 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Handler;
@@ -92,9 +91,6 @@
/** @hide */
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
- /** @hide */
- public static final int MAX_A11Y_EVENTS_PER_SERVICE_CALL = 20;
-
static final Object sInstanceSync = new Object();
private static AccessibilityManager sInstance;
@@ -103,8 +99,6 @@
private IAccessibilityManager mService;
- private EventDispatchThread mEventDispatchThread;
-
final int mUserId;
final Handler mHandler;
@@ -303,32 +297,44 @@
* their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
- if (!isEnabled()) {
- Looper myLooper = Looper.myLooper();
- if (myLooper == Looper.getMainLooper()) {
- throw new IllegalStateException(
- "Accessibility off. Did you forget to check that?");
- } else {
- // If we're not running on the thread with the main looper, it's possible for
- // the state of accessibility to change between checking isEnabled and
- // calling this method. So just log the error rather than throwing the
- // exception.
- Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
return;
}
- }
- event.setEventTime(SystemClock.uptimeMillis());
-
- getEventDispatchThread().scheduleEvent(event);
- }
-
- private EventDispatchThread getEventDispatchThread() {
- synchronized (mLock) {
- if (mEventDispatchThread == null) {
- mEventDispatchThread = new EventDispatchThread(mService, mUserId);
- mEventDispatchThread.start();
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+ return;
+ }
}
- return mEventDispatchThread;
+ userId = mUserId;
+ }
+ try {
+ event.setEventTime(SystemClock.uptimeMillis());
+ // it is possible that this manager is in the same process as the service but
+ // client using it is called through Binder from another process. Example: MMS
+ // app adds a SMS notification and the NotificationManagerService calls this method
+ long identityToken = Binder.clearCallingIdentity();
+ service.sendAccessibilityEvent(event, userId);
+ Binder.restoreCallingIdentity(identityToken);
+ if (DEBUG) {
+ Log.i(LOG_TAG, event + " sent");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error during sending " + event + " ", re);
+ } finally {
+ event.recycle();
}
}
@@ -713,99 +719,4 @@
}
}
}
-
- private static class EventDispatchThread extends Thread {
- // Second lock used to keep UI thread performant. Never try to grab mLock when holding
- // this one, or the UI thread will block in send AccessibilityEvent.
- private final Object mEventQueueLock = new Object();
-
- // Two lists to hold events. The app thread fills one while we empty the other.
- private final ArrayList<AccessibilityEvent> mEventLists0 =
- new ArrayList<>(MAX_A11Y_EVENTS_PER_SERVICE_CALL);
- private final ArrayList<AccessibilityEvent> mEventLists1 =
- new ArrayList<>(MAX_A11Y_EVENTS_PER_SERVICE_CALL);
-
- private boolean mPingPongListToggle;
-
- private final IAccessibilityManager mService;
-
- private final int mUserId;
-
- EventDispatchThread(IAccessibilityManager service, int userId) {
- mService = service;
- mUserId = userId;
- }
-
- @Override
- public void run() {
- while (true) {
- ArrayList<AccessibilityEvent> listBeingDrained;
- synchronized (mEventQueueLock) {
- ArrayList<AccessibilityEvent> listBeingFilled = getListBeingFilledLocked();
- if (listBeingFilled.isEmpty()) {
- try {
- mEventQueueLock.wait();
- } catch (InterruptedException e) {
- // Treat as a notify
- }
- }
- // Swap buffers
- mPingPongListToggle = !mPingPongListToggle;
- listBeingDrained = listBeingFilled;
- }
- dispatchEvents(listBeingDrained);
- }
- }
-
- public void scheduleEvent(AccessibilityEvent event) {
- synchronized (mEventQueueLock) {
- getListBeingFilledLocked().add(event);
- mEventQueueLock.notifyAll();
- }
- }
-
- private ArrayList<AccessibilityEvent> getListBeingFilledLocked() {
- return (mPingPongListToggle) ? mEventLists0 : mEventLists1;
- }
-
- private void dispatchEvents(ArrayList<AccessibilityEvent> events) {
- int eventListCapacityLowerBound = events.size();
- while (events.size() > 0) {
- // We don't want to consume extra memory if an app sends a lot of events in a
- // one-off event. Cap the list length at double the max events per call.
- // We'll end up with extra GC for apps that send huge numbers of events, but
- // sending that many events will lead to bad performance in any case.
- if ((eventListCapacityLowerBound > 2 * MAX_A11Y_EVENTS_PER_SERVICE_CALL)
- && (events.size() <= 2 * MAX_A11Y_EVENTS_PER_SERVICE_CALL)) {
- events.trimToSize();
- eventListCapacityLowerBound = events.size();
- }
- // We only expect this loop to run once, as the app shouldn't be sending
- // huge numbers of events.
- // The clear in the called method will remove the sent events
- dispatchOneBatchOfEvents(events.subList(0,
- Math.min(events.size(), MAX_A11Y_EVENTS_PER_SERVICE_CALL)));
- }
- }
-
- private void dispatchOneBatchOfEvents(List<AccessibilityEvent> events) {
- if (events.isEmpty()) {
- return;
- }
- long identityToken = Binder.clearCallingIdentity();
- try {
- mService.sendAccessibilityEvents(new ParceledListSlice<>(events),
- mUserId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error sending multiple events");
- }
- Binder.restoreCallingIdentity(identityToken);
- if (DEBUG) {
- Log.i(LOG_TAG, events.size() + " events sent");
- }
- for (int i = events.size() - 1; i >= 0; i--) {
- events.remove(i).recycle();
- }
- }
- }
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index aa9cb39..71e77c4 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -21,7 +21,6 @@
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
-import android.content.pm.ParceledListSlice;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -30,7 +29,7 @@
/**
* Interface implemented by the AccessibilityManagerService called by
- * the AccessibilityMasngers.
+ * the AccessibilityManagers.
*
* @hide
*/
@@ -40,8 +39,6 @@
void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
- void sendAccessibilityEvents(in ParceledListSlice events, int userId);
-
List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index d89c172..474db12 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -26,6 +26,7 @@
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.TypedValue;
+
import dalvik.system.CloseGuard;
/**
@@ -80,13 +81,13 @@
* order.
*/
public static final int ZORDER_NORMAL = 0;
-
+
/**
* Requests that the content being animated be forced on top of all other
* content for the duration of the animation.
*/
public static final int ZORDER_TOP = 1;
-
+
/**
* Requests that the content being animated be forced under all other
* content for the duration of the animation.
@@ -138,7 +139,7 @@
/**
* Indicates whether fillBefore should be taken into account.
*/
- boolean mFillEnabled = false;
+ boolean mFillEnabled = false;
/**
* The time in milliseconds at which the animation must start;
@@ -240,7 +241,7 @@
setDuration((long) a.getInt(com.android.internal.R.styleable.Animation_duration, 0));
setStartOffset((long) a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0));
-
+
setFillEnabled(a.getBoolean(com.android.internal.R.styleable.Animation_fillEnabled, mFillEnabled));
setFillBefore(a.getBoolean(com.android.internal.R.styleable.Animation_fillBefore, mFillBefore));
setFillAfter(a.getBoolean(com.android.internal.R.styleable.Animation_fillAfter, mFillAfter));
@@ -249,7 +250,7 @@
setRepeatMode(a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART));
setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL));
-
+
setBackgroundColor(a.getInt(com.android.internal.R.styleable.Animation_background, 0));
setDetachWallpaper(a.getBoolean(com.android.internal.R.styleable.Animation_detachWallpaper, false));
@@ -294,13 +295,13 @@
/**
* Cancel the animation. Cancelling an animation invokes the animation
* listener, if set, to notify the end of the animation.
- *
+ *
* If you cancel an animation manually, you must call {@link #reset()}
* before starting the animation again.
- *
- * @see #reset()
- * @see #start()
- * @see #startNow()
+ *
+ * @see #reset()
+ * @see #start()
+ * @see #startNow()
*/
public void cancel() {
if (mStarted && !mEnded) {
@@ -356,7 +357,7 @@
/**
* Sets the handler used to invoke listeners.
- *
+ *
* @hide
*/
public void setListenerHandler(Handler handler) {
@@ -424,7 +425,7 @@
/**
* How long this animation should last. The duration cannot be negative.
- *
+ *
* @param durationMillis Duration in milliseconds
*
* @throws java.lang.IllegalArgumentException if the duration is < 0
@@ -443,7 +444,7 @@
* than <var>durationMillis</var>. In addition to adjusting the duration
* itself, this ensures that the repeat count also will not make it run
* longer than the given time.
- *
+ *
* @param durationMillis The maximum duration the animation is allowed
* to run.
*/
@@ -455,7 +456,7 @@
mRepeatCount = 0;
return;
}
-
+
long dur = mDuration + mStartOffset;
if (dur > durationMillis) {
mDuration = durationMillis-mStartOffset;
@@ -480,7 +481,7 @@
}
}
}
-
+
/**
* How much to scale the duration by.
*
@@ -528,7 +529,7 @@
/**
* Defines what this animation should do when it reaches the end. This
* setting is applied only when the repeat count is either greater than
- * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+ * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
*
* @param repeatMode {@link #RESTART} or {@link #REVERSE}
* @attr ref android.R.styleable#Animation_repeatMode
@@ -606,7 +607,7 @@
* @param fillAfter true if the animation should apply its transformation after it ends
* @attr ref android.R.styleable#Animation_fillAfter
*
- * @see #setFillEnabled(boolean)
+ * @see #setFillEnabled(boolean)
*/
public void setFillAfter(boolean fillAfter) {
mFillAfter = fillAfter;
@@ -614,7 +615,7 @@
/**
* Set the Z ordering mode to use while running the animation.
- *
+ *
* @param zAdjustment The desired mode, one of {@link #ZORDER_NORMAL},
* {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
* @attr ref android.R.styleable#Animation_zAdjustment
@@ -622,7 +623,7 @@
public void setZAdjustment(int zAdjustment) {
mZAdjustment = zAdjustment;
}
-
+
/**
* Set background behind animation.
*
@@ -634,11 +635,11 @@
}
/**
- * The scale factor is set by the call to <code>getTransformation</code>. Overrides of
+ * The scale factor is set by the call to <code>getTransformation</code>. Overrides of
* {@link #getTransformation(long, Transformation, float)} will get this value
* directly. Overrides of {@link #applyTransformation(float, Transformation)} can
* call this method to get the value.
- *
+ *
* @return float The scale factor that should be applied to pre-scaled values in
* an Animation such as the pivot points in {@link ScaleAnimation} and {@link RotateAnimation}.
*/
@@ -748,7 +749,7 @@
/**
* Returns the Z ordering mode to use while running the animation as
* previously set by {@link #setZAdjustment}.
- *
+ *
* @return Returns one of {@link #ZORDER_NORMAL},
* {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
* @attr ref android.R.styleable#Animation_zAdjustment
@@ -827,7 +828,7 @@
public long computeDurationHint() {
return (getStartOffset() + getDuration()) * (getRepeatCount() + 1);
}
-
+
/**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
@@ -975,7 +976,7 @@
* their transforms given an interpolation value. Implementations of this
* method should always replace the specified Transformation or document
* they are doing otherwise.
- *
+ *
* @param interpolatedTime The value of the normalized time (0.0 to 1.0)
* after it has been run through the interpolation function.
* @param t The Transformation object to fill in with the current
@@ -1015,7 +1016,7 @@
* @param bottom
* @param invalidate
* @param transformation
- *
+ *
* @hide
*/
public void getInvalidateRegion(int left, int top, int right, int bottom,
@@ -1072,7 +1073,7 @@
/**
* Return true if this animation changes the view's alpha property.
- *
+ *
* @hide
*/
public boolean hasAlpha() {
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 71c7450..09d4dfc 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -18,17 +18,17 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
-import android.graphics.RectF;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a group of Animations that should be played together.
- * The transformation of each individual animation are composed
- * together into a single transform.
+ * The transformation of each individual animation are composed
+ * together into a single transform.
* If AnimationSet sets any properties that its children also set
* (for example, duration or fillBefore), the values of AnimationSet
* override the child values.
@@ -72,17 +72,17 @@
private long[] mStoredOffsets;
/**
- * Constructor used when an AnimationSet is loaded from a resource.
- *
+ * Constructor used when an AnimationSet is loaded from a resource.
+ *
* @param context Application context to use
* @param attrs Attribute set from which to read values
*/
public AnimationSet(Context context, AttributeSet attrs) {
super(context, attrs);
-
+
TypedArray a =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet);
-
+
setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
init();
@@ -108,11 +108,11 @@
a.recycle();
}
-
-
+
+
/**
* Constructor to use when building an AnimationSet from code
- *
+ *
* @param shareInterpolator Pass true if all of the animations in this set
* should use the interpolator associated with this AnimationSet.
* Pass false if each animation should use its own interpolator.
@@ -244,10 +244,10 @@
mDirty = true;
}
-
+
/**
* Sets the start time of this animation and all child animations
- *
+ *
* @see android.view.animation.Animation#setStartTime(long)
*/
@Override
@@ -289,11 +289,11 @@
animations.get(i).restrictDuration(durationMillis);
}
}
-
+
/**
- * The duration of an AnimationSet is defined to be the
+ * The duration of an AnimationSet is defined to be the
* duration of the longest child animation.
- *
+ *
* @see android.view.animation.Animation#getDuration()
*/
@Override
@@ -317,7 +317,7 @@
/**
* The duration hint of an animation set is the maximum of the duration
* hints of all of its component animations.
- *
+ *
* @see android.view.animation.Animation#computeDurationHint
*/
public long computeDurationHint() {
@@ -362,7 +362,7 @@
/**
* The transformation of an animation set is the concatenation of all of its
* component animations.
- *
+ *
* @see android.view.animation.Animation#getTransformation
*/
@Override
@@ -404,7 +404,7 @@
return more;
}
-
+
/**
* @see android.view.animation.Animation#scaleCurrentDuration(float)
*/
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 351b6db..f5c3613 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,19 +16,19 @@
package android.view.animation;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.AnimRes;
import android.annotation.InterpolatorRes;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
-import android.content.res.Resources.NotFoundException;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Xml;
-import android.os.SystemClock;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index fb66c31..7a837c3 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -18,8 +18,8 @@
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import com.android.internal.R;
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 1af72da..9a75134 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -16,6 +16,10 @@
package android.view.animation;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_extraTension;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_tension;
+
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -26,10 +30,6 @@
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_extraTension;
-import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_tension;
-import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
-
/**
* An interpolator where the change starts backward then flings forward and overshoots
* the target value and finally goes back to the final value.
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index 663c109..72d64a1 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -18,8 +18,8 @@
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import com.android.internal.R;
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index f426f60..f89743c 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -18,8 +18,8 @@
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import com.android.internal.R;
diff --git a/core/java/android/view/animation/GridLayoutAnimationController.java b/core/java/android/view/animation/GridLayoutAnimationController.java
index 9161d8b..0f189ae 100644
--- a/core/java/android/view/animation/GridLayoutAnimationController.java
+++ b/core/java/android/view/animation/GridLayoutAnimationController.java
@@ -16,11 +16,11 @@
package android.view.animation;
-import android.view.View;
-import android.view.ViewGroup;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
import java.util.Random;
@@ -43,7 +43,7 @@
*
* @see LayoutAnimationController
* @see android.widget.GridView
- *
+ *
* @attr ref android.R.styleable#GridLayoutAnimation_columnDelay
* @attr ref android.R.styleable#GridLayoutAnimation_rowDelay
* @attr ref android.R.styleable#GridLayoutAnimation_direction
@@ -206,7 +206,7 @@
*
* @see #getRowDelay()
* @see #getColumnDelay()
- * @see #setColumnDelay(float)
+ * @see #setColumnDelay(float)
*/
public void setRowDelay(float rowDelay) {
mRowDelay = rowDelay;
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 38962a3..5f7a0f7 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2008 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
@@ -35,7 +35,6 @@
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-import android.view.ViewRootImpl;
class ComposingText implements NoCopySpan {
}
@@ -56,25 +55,25 @@
protected final InputMethodManager mIMM;
final View mTargetView;
final boolean mDummyMode;
-
+
private Object[] mDefaultComposingSpans;
-
+
Editable mEditable;
KeyCharacterMap mKeyCharacterMap;
-
+
BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
mIMM = mgr;
mTargetView = null;
mDummyMode = !fullEditor;
}
-
+
public BaseInputConnection(View targetView, boolean fullEditor) {
mIMM = (InputMethodManager)targetView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
mTargetView = targetView;
mDummyMode = !fullEditor;
}
-
+
public static final void removeComposingSpans(Spannable text) {
text.removeSpan(COMPOSING);
Object[] sps = text.getSpans(0, text.length(), Object.class);
@@ -104,8 +103,8 @@
}
final int fl = text.getSpanFlags(o);
- if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
- != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
+ if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK))
+ != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
(fl & ~Spanned.SPAN_POINT_MARK_MASK)
| Spanned.SPAN_COMPOSING
@@ -117,15 +116,15 @@
text.setSpan(COMPOSING, start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
}
-
+
public static int getComposingSpanStart(Spannable text) {
return text.getSpanStart(COMPOSING);
}
-
+
public static int getComposingSpanEnd(Spannable text) {
return text.getSpanEnd(COMPOSING);
}
-
+
/**
* Return the target of edit operations. The default implementation
* returns its own fake editable that is just used for composing text;
@@ -139,7 +138,7 @@
}
return mEditable;
}
-
+
/**
* Default implementation does nothing.
*/
@@ -452,10 +451,10 @@
*/
public int getCursorCapsMode(int reqModes) {
if (mDummyMode) return 0;
-
+
final Editable content = getEditable();
if (content == null) return 0;
-
+
int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);
@@ -495,7 +494,7 @@
if (a <= 0) {
return "";
}
-
+
if (length > a) {
length = a;
}
@@ -702,7 +701,7 @@
if (!mDummyMode) {
return;
}
-
+
Editable content = getEditable();
if (content != null) {
final int N = content.length();
@@ -727,7 +726,7 @@
return;
}
}
-
+
// Otherwise, revert to the special key event containing
// the actual characters.
KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
@@ -768,7 +767,7 @@
if (content == null) {
return;
}
-
+
beginBatchEdit();
// delete composing text set previously.
@@ -776,7 +775,7 @@
int b = getComposingSpanEnd(content);
if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
-
+
if (b < a) {
int tmp = a;
a = b;
@@ -814,11 +813,11 @@
}
setComposingSpans(sp);
}
-
+
if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
+ text + "\", composing=" + composing
+ ", type=" + text.getClass().getCanonicalName());
-
+
if (DEBUG) {
LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
lp.println("Current text:");
@@ -842,13 +841,13 @@
Selection.setSelection(content, newCursorPosition);
content.replace(a, b, text);
-
+
if (DEBUG) {
LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
lp.println("Final text:");
TextUtils.dumpSpans(content, lp, " ");
}
-
+
endBatchEdit();
}
diff --git a/core/java/android/view/inputmethod/InputConnectionInspector.java b/core/java/android/view/inputmethod/InputConnectionInspector.java
index 2b292bb..5f25bf5 100644
--- a/core/java/android/view/inputmethod/InputConnectionInspector.java
+++ b/core/java/android/view/inputmethod/InputConnectionInspector.java
@@ -16,6 +16,8 @@
package android.view.inputmethod;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,8 +30,6 @@
import java.util.Map;
import java.util.WeakHashMap;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
/**
* @hide
*/
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 661b52f..5c8e6dc 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -16,9 +16,6 @@
package android.view.inputmethod;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -39,6 +36,9 @@
import android.util.Xml;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ca370dc..2e99092 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2007-2008 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
@@ -16,15 +16,7 @@
package android.view.inputmethod;
-import com.android.internal.inputmethod.IInputContentUriToken;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputConnectionWrapper;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodClient;
-import com.android.internal.view.IInputMethodManager;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.InputBindResult;
-import com.android.internal.view.InputMethodClient;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,8 +32,8 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
-import android.os.Trace;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.Trace;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
@@ -56,11 +48,19 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
-import android.view.textservice.TextServicesManager;
+
+import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputConnectionWrapper;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodClient;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -70,14 +70,12 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-
/**
* Central system API to the overall input method framework (IMF) architecture,
* which arbitrates interaction between applications and the current input method.
* You can retrieve an instance of this interface with
* {@link Context#getSystemService(String) Context.getSystemService()}.
- *
+ *
* <p>Topics covered here:
* <ol>
* <li><a href="#ArchitectureOverview">Architecture Overview</a>
@@ -85,13 +83,13 @@
* <li><a href="#InputMethods">Input Methods</a>
* <li><a href="#Security">Security</a>
* </ol>
- *
+ *
* <a name="ArchitectureOverview"></a>
* <h3>Architecture Overview</h3>
- *
+ *
* <p>There are three primary parties involved in the input method
* framework (IMF) architecture:</p>
- *
+ *
* <ul>
* <li> The <strong>input method manager</strong> as expressed by this class
* is the central point of the system that manages interaction between all
@@ -106,16 +104,16 @@
* method manager for input focus and control over the state of the IME. Only
* one such client is ever active (working with the IME) at a time.
* </ul>
- *
- *
+ *
+ *
* <a name="Applications"></a>
* <h3>Applications</h3>
- *
+ *
* <p>In most cases, applications that are using the standard
* {@link android.widget.TextView} or its subclasses will have little they need
* to do to work well with soft input methods. The main things you need to
* be aware of are:</p>
- *
+ *
* <ul>
* <li> Properly set the {@link android.R.attr#inputType} in your editable
* text views, so that the input method will have enough context to help the
@@ -131,43 +129,43 @@
* for your window using the same {@link android.R.attr#windowSoftInputMode}
* attribute.
* </ul>
- *
+ *
* <p>More finer-grained control is available through the APIs here to directly
* interact with the IMF and its IME -- either showing or hiding the input
* area, letting the user pick an input method, etc.</p>
- *
+ *
* <p>For the rare people amongst us writing their own text editors, you
* will need to implement {@link android.view.View#onCreateInputConnection}
* to return a new instance of your own {@link InputConnection} interface
* allowing the IME to interact with your editor.</p>
- *
- *
+ *
+ *
* <a name="InputMethods"></a>
* <h3>Input Methods</h3>
- *
+ *
* <p>An input method (IME) is implemented
* as a {@link android.app.Service}, typically deriving from
* {@link android.inputmethodservice.InputMethodService}. It must provide
* the core {@link InputMethod} interface, though this is normally handled by
* {@link android.inputmethodservice.InputMethodService} and implementors will
* only need to deal with the higher-level API there.</p>
- *
+ *
* See the {@link android.inputmethodservice.InputMethodService} class for
* more information on implementing IMEs.
- *
- *
+ *
+ *
* <a name="Security"></a>
* <h3>Security</h3>
- *
+ *
* <p>There are a lot of security issues associated with input methods,
* since they essentially have freedom to completely drive the UI and monitor
* everything the user enters. The Android input method framework also allows
* arbitrary third party IMEs, so care must be taken to restrict their
* selection and interactions.</p>
- *
+ *
* <p>Here are some key points about the security architecture behind the
* IMF:</p>
- *
+ *
* <ul>
* <li> <p>Only the system is allowed to directly access an IME's
* {@link InputMethod} interface, via the
@@ -175,11 +173,11 @@
* enforced in the system by not binding to an input method service that does
* not require this permission, so the system can guarantee no other untrusted
* clients are accessing the current input method outside of its control.</p>
- *
+ *
* <li> <p>There may be many client processes of the IMF, but only one may
* be active at a time. The inactive clients can not interact with key
* parts of the IMF through the mechanisms described below.</p>
- *
+ *
* <li> <p>Clients of an input method are only given access to its
* {@link InputMethodSession} interface. One instance of this interface is
* created for each client, and only calls from the session associated with
@@ -187,19 +185,19 @@
* by {@link android.inputmethodservice.AbstractInputMethodService} for normal
* IMEs, but must be explicitly handled by an IME that is customizing the
* raw {@link InputMethodSession} implementation.</p>
- *
+ *
* <li> <p>Only the active client's {@link InputConnection} will accept
* operations. The IMF tells each client process whether it is active, and
* the framework enforces that in inactive processes calls on to the current
* InputConnection will be ignored. This ensures that the current IME can
* only deliver events and text edits to the UI that the user sees as
* being in focus.</p>
- *
+ *
* <li> <p>An IME can never interact with an {@link InputConnection} while
* the screen is off. This is enforced by making all clients inactive while
* the screen is off, and prevents bad IMEs from driving the UI when the user
* can not be aware of its behavior.</p>
- *
+ *
* <li> <p>A client application can ask that the system let the user pick a
* new IME, but can not programmatically switch to one itself. This avoids
* malicious applications from switching the user to their own IME, which
@@ -207,7 +205,7 @@
* IME, on the other hand, <em>is</em> allowed to programmatically switch
* the system to another IME, since it already has full control of user
* input.</p>
- *
+ *
* <li> <p>The user must explicitly enable a new IME in settings before
* they can switch to it, to confirm with the system that they know about it
* and want to make it available for use.</p>
@@ -268,11 +266,11 @@
final IInputMethodManager mService;
final Looper mMainLooper;
-
+
// For scheduling work on the main thread. This also serves as our
// global lock.
final H mH;
-
+
// Our generic input connection if the current target does not have its own.
final IInputContext mIInputContext;
@@ -280,20 +278,20 @@
* True if this input method client is active, initially false.
*/
boolean mActive = false;
-
+
/**
* Set whenever this client becomes inactive, to know we need to reset
* state with the IME the next time we receive focus.
*/
boolean mHasBeenInactive = true;
-
+
/**
* As reported by IME through InputConnection.
*/
boolean mFullscreenMode;
-
+
// -----------------------------------------------------------
-
+
/**
* This is the root view of the overall window that currently has input
* method focus.
@@ -328,7 +326,7 @@
* The completions that were last provided by the served view.
*/
CompletionInfo[] mCompletions;
-
+
// Cursor position on the screen.
Rect mTmpCursorRect = new Rect();
Rect mCursorRect = new Rect();
@@ -389,7 +387,7 @@
final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
// -----------------------------------------------------------
-
+
static final int MSG_DUMP = 1;
static final int MSG_BIND = 2;
static final int MSG_UNBIND = 3;
@@ -403,7 +401,7 @@
H(Looper looper) {
super(looper, null, true);
}
-
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -654,7 +652,7 @@
return sInstance;
}
}
-
+
/**
* Private optimization: retrieve the global InputMethodManager instance,
* if it exists.
@@ -663,17 +661,17 @@
public static InputMethodManager peekInstance() {
return sInstance;
}
-
+
/** @hide */
public IInputMethodClient getClient() {
return mClient;
}
-
+
/** @hide */
public IInputContext getInputContext() {
return mIInputContext;
}
-
+
public List<InputMethodInfo> getInputMethodList() {
try {
return mService.getInputMethodList();
@@ -784,7 +782,7 @@
&& mCurrentTextBoxAttribute != null;
}
}
-
+
/**
* Return true if any view is currently active in the input method.
*/
@@ -794,7 +792,7 @@
return mServedView != null && mCurrentTextBoxAttribute != null;
}
}
-
+
/**
* Return true if the currently served view is accepting full text edits.
* If false, it has no input connection, so can only handle raw key events.
@@ -871,7 +869,7 @@
|| !mServedView.checkInputConnectionProxy(view))) {
return;
}
-
+
mCompletions = completions;
if (mCurMethod != null) {
try {
@@ -881,7 +879,7 @@
}
}
}
-
+
public void updateExtractedText(View view, int token, ExtractedText text) {
checkFocus();
synchronized (mH) {
@@ -889,7 +887,7 @@
|| !mServedView.checkInputConnectionProxy(view))) {
return;
}
-
+
if (mCurMethod != null) {
try {
mCurMethod.updateExtractedText(token, text);
@@ -898,26 +896,26 @@
}
}
}
-
+
/**
* Flag for {@link #showSoftInput} to indicate that this is an implicit
* request to show the input window, not as the result of a direct request
* by the user. The window may not be shown in this case.
*/
public static final int SHOW_IMPLICIT = 0x0001;
-
+
/**
* Flag for {@link #showSoftInput} to indicate that the user has forced
* the input method open (such as by long-pressing menu) so it should
* not be closed until they explicitly do so.
*/
public static final int SHOW_FORCED = 0x0002;
-
+
/**
* Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
* a result receiver: explicitly request that the current input method's
* soft input area be shown to the user, if needed.
- *
+ *
* @param view The currently focused view, which would like to receive
* soft keyboard input.
* @param flags Provides additional operating flags. Currently may be
@@ -926,7 +924,7 @@
public boolean showSoftInput(View view, int flags) {
return showSoftInput(view, flags, null);
}
-
+
/**
* Flag for the {@link ResultReceiver} result code from
* {@link #showSoftInput(View, int, ResultReceiver)} and
@@ -934,7 +932,7 @@
* state of the soft input window was unchanged and remains shown.
*/
public static final int RESULT_UNCHANGED_SHOWN = 0;
-
+
/**
* Flag for the {@link ResultReceiver} result code from
* {@link #showSoftInput(View, int, ResultReceiver)} and
@@ -942,7 +940,7 @@
* state of the soft input window was unchanged and remains hidden.
*/
public static final int RESULT_UNCHANGED_HIDDEN = 1;
-
+
/**
* Flag for the {@link ResultReceiver} result code from
* {@link #showSoftInput(View, int, ResultReceiver)} and
@@ -950,7 +948,7 @@
* state of the soft input window changed from hidden to shown.
*/
public static final int RESULT_SHOWN = 2;
-
+
/**
* Flag for the {@link ResultReceiver} result code from
* {@link #showSoftInput(View, int, ResultReceiver)} and
@@ -958,7 +956,7 @@
* state of the soft input window changed from shown to hidden.
*/
public static final int RESULT_HIDDEN = 3;
-
+
/**
* Explicitly request that the current input method's soft input area be
* shown to the user, if needed. Call this if the user interacts with
@@ -1000,7 +998,7 @@
}
}
}
-
+
/** @hide */
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
try {
@@ -1009,14 +1007,14 @@
throw e.rethrowFromSystemServer();
}
}
-
+
/**
* Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
* input window should only be hidden if it was not explicitly shown
* by the user.
*/
public static final int HIDE_IMPLICIT_ONLY = 0x0001;
-
+
/**
* Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
* input window should normally be hidden, unless it was originally
@@ -1028,7 +1026,7 @@
* Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}
* without a result: request to hide the soft input window from the
* context of the window that is currently accepting input.
- *
+ *
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
* @param flags Provides additional operating flags. Currently may be
@@ -1037,7 +1035,7 @@
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
return hideSoftInputFromWindow(windowToken, flags, null);
}
-
+
/**
* Request to hide the soft input window from the context of the window
* that is currently accepting input. This should be called as a result
@@ -1079,11 +1077,11 @@
}
}
}
-
+
/**
* This method toggles the input method window display.
- * If the input window is already displayed, it gets hidden.
+ * If the input window is already displayed, it gets hidden.
* If not the input window will be displayed.
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
@@ -1110,7 +1108,7 @@
/*
* This method toggles the input method window display.
- * If the input window is already displayed, it gets hidden.
+ * If the input window is already displayed, it gets hidden.
* If not the input window will be displayed.
* @param showFlags Provides additional operating flags. May be
* 0 or have the {@link #SHOW_IMPLICIT},
@@ -1134,7 +1132,7 @@
* restart it with its new contents. You should call this when the text
* within your view changes outside of the normal input method or key
* input flow, such as when an application calls TextView.setText().
- *
+ *
* @param view The view whose text has changed.
*/
public void restartInput(View view) {
@@ -1144,7 +1142,7 @@
|| !mServedView.checkInputConnectionProxy(view))) {
return;
}
-
+
mServedConnecting = true;
}
@@ -1196,7 +1194,7 @@
});
return false;
}
-
+
// Okay we are now ready to call into the served view and have it
// do its stuff.
// Life is good: let's hook everything up!
@@ -1465,7 +1463,7 @@
return true;
}
-
+
void closeCurrentInput() {
try {
mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS, null);
@@ -1504,7 +1502,7 @@
if (first) {
controlFlags |= CONTROL_WINDOW_FIRST;
}
-
+
if (checkFocusNoStartInput(forceNewFocus)) {
// We need to restart input on the current focus view. This
// should be done in conjunction with telling the system service
@@ -1792,7 +1790,7 @@
* Close/hide the input method's soft input area, so the user no longer
* sees it or can interact with it. This can only be called
* from the currently active input method, as validated by the given token.
- *
+ *
* @param token Supplies the identifying token given to an input method
* when it was started, which allows it to perform this operation on
* itself.
@@ -1807,13 +1805,13 @@
throw e.rethrowFromSystemServer();
}
}
-
+
/**
- * Show the input method's soft input area, so the user
+ * Show the input method's soft input area, so the user
* sees the input method window and can interact with it.
* This can only be called from the currently active input method,
* as validated by the given token.
- *
+ *
* @param token Supplies the identifying token given to an input method
* when it was started, which allows it to perform this operation on
* itself.
@@ -2335,7 +2333,7 @@
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
-
+
p.println(" mService=" + mService);
p.println(" mMainLooper=" + mMainLooper);
p.println(" mIInputContext=" + mIInputContext);
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index fc17f7a..7aa2c23 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -16,9 +16,6 @@
package android.view.textservice;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -35,6 +32,9 @@
import android.util.Slog;
import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index e77dc0d..729eb8d 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -16,11 +16,6 @@
package android.view.textservice;
-import com.android.internal.textservice.ISpellCheckerSession;
-import com.android.internal.textservice.ISpellCheckerSessionListener;
-import com.android.internal.textservice.ITextServicesManager;
-import com.android.internal.textservice.ITextServicesSessionListener;
-
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -29,6 +24,11 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
import java.util.LinkedList;
import java.util.Queue;
diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java
index 8dff0c6..026610e 100644
--- a/core/java/android/view/textservice/SpellCheckerSubtype.java
+++ b/core/java/android/view/textservice/SpellCheckerSubtype.java
@@ -16,8 +16,6 @@
package android.view.textservice;
-import com.android.internal.inputmethod.InputMethodUtils;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -27,6 +25,8 @@
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.inputmethod.InputMethodUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java
index 78bc1a9..dc2051c 100644
--- a/core/java/android/view/textservice/SuggestionsInfo.java
+++ b/core/java/android/view/textservice/SuggestionsInfo.java
@@ -16,11 +16,11 @@
package android.view.textservice;
-import com.android.internal.util.ArrayUtils;
-
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.ArrayUtils;
+
/**
* This class contains a metadata of suggestions from the text service
*/
diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl
index 9434f0c..894f080 100644
--- a/core/java/android/webkit/IWebViewUpdateService.aidl
+++ b/core/java/android/webkit/IWebViewUpdateService.aidl
@@ -64,6 +64,11 @@
String getCurrentWebViewPackageName();
/**
+ * Used by public API for debugging purposes.
+ */
+ PackageInfo getCurrentWebViewPackage();
+
+ /**
* Used by Settings to determine whether a certain package can be enabled/disabled by the user -
* the package should not be modifiable in this way if it is a fallback package.
*/
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index aaa7b26..b840f4a 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,6 +21,7 @@
import android.annotation.Widget;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -36,6 +37,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.StrictMode;
+import android.os.RemoteException;
import android.print.PrintDocumentAdapter;
import android.security.KeyChain;
import android.util.AttributeSet;
@@ -1768,6 +1770,7 @@
* requests. This will replace the current handler.
*
* @param client an implementation of WebViewClient
+ * @see #getWebViewClient
*/
public void setWebViewClient(WebViewClient client) {
checkThread();
@@ -1775,6 +1778,17 @@
}
/**
+ * Gets the WebViewClient.
+ *
+ * @return the WebViewClient, or a default client if not yet set
+ * @see #setWebViewClient
+ */
+ public WebViewClient getWebViewClient() {
+ checkThread();
+ return mProvider.getWebViewClient();
+ }
+
+ /**
* Registers the interface to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current handler.
@@ -1792,6 +1806,7 @@
* This will replace the current handler.
*
* @param client an implementation of WebChromeClient
+ * @see #getWebChromeClient
*/
public void setWebChromeClient(WebChromeClient client) {
checkThread();
@@ -1799,6 +1814,17 @@
}
/**
+ * Gets the chrome handler.
+ *
+ * @return the WebChromeClient, or null if not yet set
+ * @see #setWebChromeClient
+ */
+ public WebChromeClient getWebChromeClient() {
+ checkThread();
+ return mProvider.getWebChromeClient();
+ }
+
+ /**
* Sets the Picture listener. This is an interface used to receive
* notifications of a new Picture.
*
@@ -2540,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);
@@ -2650,6 +2683,26 @@
}
/**
+ * If WebView has already been loaded into the current process this method will return the
+ * package that was used to load it. Otherwise, the package that would be used if the WebView
+ * was loaded right now will be returned; this does not cause WebView to be loaded, so this
+ * information may become outdated at any time.
+ * @return the current WebView package, or null if there is none.
+ */
+ public static PackageInfo getCurrentWebViewPackage() {
+ PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
+ if (webviewPackage != null) {
+ return webviewPackage;
+ }
+
+ try {
+ return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
*
* @param requestCode The integer request code originally supplied to
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 4bfc718..f4ea90b 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -135,7 +135,9 @@
}
public static PackageInfo getLoadedPackageInfo() {
- return sPackageInfo;
+ synchronized (sProviderLock) {
+ return sPackageInfo;
+ }
}
/**
@@ -170,9 +172,8 @@
Log.e(LOGTAG, "Couldn't find package " + packageName);
return LIBLOAD_WRONG_PACKAGE_NAME;
}
- sPackageInfo = packageInfo;
- int loadNativeRet = loadNativeLibrary(clazzLoader);
+ int loadNativeRet = loadNativeLibrary(clazzLoader, packageInfo);
// If we failed waiting for relro we want to return that fact even if we successfully load
// the relro file.
if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
@@ -358,7 +359,7 @@
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
- loadNativeLibrary(clazzLoader);
+ loadNativeLibrary(clazzLoader, sPackageInfo);
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
@@ -489,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);
}
@@ -637,14 +641,14 @@
}
}
- // Assumes that we have waited for relro creation and set sPackageInfo
- private static int loadNativeLibrary(ClassLoader clazzLoader) {
+ // Assumes that we have waited for relro creation
+ private static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo) {
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't load with relro file; address space not reserved");
return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
}
- String[] args = getWebViewNativeLibraryPaths(sPackageInfo);
+ String[] args = getWebViewNativeLibraryPaths(packageInfo);
int result = nativeLoadWithRelroFile(args[0] /* path32 */,
args[1] /* path64 */,
CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index e5b65e7..95ec179 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -229,10 +229,14 @@
public void setWebViewClient(WebViewClient client);
+ public WebViewClient getWebViewClient();
+
public void setDownloadListener(DownloadListener listener);
public void setWebChromeClient(WebChromeClient client);
+ public WebChromeClient getWebChromeClient();
+
public void setPictureListener(PictureListener listener);
public void addJavascriptInterface(Object obj, String interfaceName);
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/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 01a95a4..5d136dc 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -36,6 +34,8 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
/**
* AbsSeekBar extends the capabilities of ProgressBar by adding a draggable thumb.
@@ -469,7 +469,7 @@
/**
* Returns the amount of progress changed via the arrow keys.
* <p>
- * By default, this will be a value that is derived from the max progress.
+ * By default, this will be a value that is derived from the progress range.
*
* @return The amount to increment or decrement when the user presses the
* arrow keys. This will be positive.
@@ -479,13 +479,27 @@
}
@Override
- public synchronized void setMax(int max) {
- super.setMax(max);
+ public synchronized void setMin(int min) {
+ super.setMin(min);
+ int range = getMax() - getMin();
- if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
+ if ((mKeyProgressIncrement == 0) || (range / mKeyProgressIncrement > 20)) {
+
// It will take the user too long to change this via keys, change it
// to something more reasonable
- setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
+ setKeyProgressIncrement(Math.max(1, Math.round((float) range / 20)));
+ }
+ }
+
+ @Override
+ public synchronized void setMax(int max) {
+ super.setMax(max);
+ int range = getMax() - getMin();
+
+ if ((mKeyProgressIncrement == 0) || (range / mKeyProgressIncrement > 20)) {
+ // It will take the user too long to change this via keys, change it
+ // to something more reasonable
+ setKeyProgressIncrement(Math.max(1, Math.round((float) range / 20)));
}
}
@@ -596,8 +610,10 @@
}
private float getScale() {
- final int max = getMax();
- return max > 0 ? getProgress() / (float) max : 0;
+ int min = getMin();
+ int max = getMax();
+ int range = max - min;
+ return range > 0 ? (getProgress() - min) / (float) range : 0;
}
/**
@@ -691,7 +707,7 @@
*/
void drawTickMarks(Canvas canvas) {
if (mTickMark != null) {
- final int count = getMax();
+ final int count = getMax() - getMin();
if (count > 1) {
final int w = mTickMark.getIntrinsicWidth();
final int h = mTickMark.getIntrinsicHeight();
@@ -847,8 +863,8 @@
}
}
- final int max = getMax();
- progress += scale * max;
+ final int range = getMax() - getMin();
+ progress += scale * range;
setHotspot(x, y);
setProgressInternal(Math.round(progress), true, false);
@@ -922,7 +938,7 @@
if (isEnabled()) {
final int progress = getProgress();
- if (progress > 0) {
+ if (progress > getMin()) {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
}
if (progress < getMax()) {
@@ -960,7 +976,8 @@
if (!canUserSetProgress()) {
return false;
}
- int increment = Math.max(1, Math.round((float) getMax() / 20));
+ int range = getMax() - getMin();
+ int increment = Math.max(1, Math.round((float) range / 20));
if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
increment = -increment;
}
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 18db54e..bc3dfff 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
@@ -29,10 +27,12 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.R;
+
/**
* An abstract base class for spinner widgets. SDK users will probably not
* need to use this class.
- *
+ *
* @attr ref android.R.styleable#AbsSpinner_entries
*/
public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
@@ -104,12 +104,12 @@
mAdapter.unregisterDataSetObserver(mDataSetObserver);
resetList();
}
-
+
mAdapter = adapter;
-
+
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
-
+
if (mAdapter != null) {
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
@@ -122,14 +122,14 @@
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
-
+
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
-
+
} else {
- checkFocus();
+ checkFocus();
resetList();
// Nothing selected
checkSelectionChanged();
@@ -144,23 +144,23 @@
void resetList() {
mDataChanged = false;
mNeedSync = false;
-
+
removeAllViewsInLayout();
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
-
+
setSelectedPositionInt(INVALID_POSITION);
setNextSelectedPositionInt(INVALID_POSITION);
invalidate();
}
- /**
+ /**
* @see android.view.View#measure(int, int)
- *
+ *
* Figure out the dimensions of this Spinner. The width comes from
* the widthMeasureSpec as Spinnners can't have their width set to
* UNSPECIFIED. The height is based on the height of the selected item
- * plus padding.
+ * plus padding.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@@ -180,11 +180,11 @@
if (mDataChanged) {
handleDataChanged();
}
-
+
int preferredHeight = 0;
int preferredWidth = 0;
boolean needsMeasuring = true;
-
+
int selectedPosition = getSelectedItemPosition();
if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
// Try looking in the recycler. (Maybe we were measured once already)
@@ -208,14 +208,14 @@
mBlockLayoutRequests = false;
}
measureChild(view, widthMeasureSpec, heightMeasureSpec);
-
+
preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
-
+
needsMeasuring = false;
}
}
-
+
if (needsMeasuring) {
// No views -- just use padding
preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
@@ -238,18 +238,18 @@
int getChildHeight(View child) {
return child.getMeasuredHeight();
}
-
+
int getChildWidth(View child) {
return child.getMeasuredWidth();
}
-
+
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
-
+
void recycleAllViews() {
final int childCount = getChildCount();
final AbsSpinner.RecycleBin recycleBin = mRecycler;
@@ -260,7 +260,7 @@
View v = getChildAt(i);
int index = position + i;
recycleBin.put(index, v);
- }
+ }
}
/**
@@ -279,14 +279,14 @@
requestLayout();
invalidate();
}
-
+
/**
* Makes the item at the supplied position selected.
- *
+ *
* @param position Position to select
* @param animate Should the transition be animated
- *
+ *
*/
void setSelectionInt(int position, boolean animate) {
if (position != mOldSelectedPosition) {
@@ -308,11 +308,11 @@
return null;
}
}
-
+
/**
* Override to prevent spamming ourselves with layout requests
* as we place views
- *
+ *
* @see android.view.View#requestLayout()
*/
@Override
@@ -334,7 +334,7 @@
/**
* Maps a point to a position in the list.
- *
+ *
* @param x X in local coordinate
* @param y Y in local coordinate
* @return The position of the item which contains the specified point, or
@@ -378,7 +378,7 @@
SavedState(Parcelable superState) {
super(superState);
}
-
+
/**
* Constructor called from {@link #CREATOR}
*/
@@ -431,7 +431,7 @@
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
-
+
super.onRestoreInstanceState(ss.getSuperState());
if (ss.selectedId >= 0) {
@@ -450,7 +450,7 @@
public void put(int position, View v) {
mScrapHeap.put(position, v);
}
-
+
View get(int position) {
// System.out.print("Looking for " + position);
View result = mScrapHeap.get(position);
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index ac8d578..46269c6 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -39,6 +39,7 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
+
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.ActionMenuItemView;
import com.android.internal.view.menu.BaseMenuPresenter;
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 4d0a1c8..c4bbdb0 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -31,6 +31,7 @@
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
+
import com.android.internal.view.menu.ActionMenuItemView;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
@@ -45,7 +46,7 @@
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
-
+
static final int MIN_CELL_SIZE = 56; // dips
static final int GENERATED_ITEM_PADDING = 4; // dips
@@ -71,7 +72,7 @@
public ActionMenuView(Context context) {
this(context, null);
}
-
+
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
setBaselineAligned(false);
@@ -579,7 +580,7 @@
params.gravity = Gravity.CENTER_VERTICAL;
return params;
}
-
+
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index f5c46db..51587a7 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -16,9 +16,6 @@
package android.widget;
-import com.android.internal.R;
-import com.android.internal.view.menu.ShowableListMenu;
-
import android.annotation.StringRes;
import android.content.Context;
import android.content.Intent;
@@ -39,6 +36,9 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ActivityChooserModel.ActivityChooserModelClient;
+import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
+
/**
* This class is a view for choosing an activity for handling a given {@link Intent}.
* <p>
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 7f5e2133..bde5f7f 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -16,15 +16,14 @@
package android.widget;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.BroadcastReceiver;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.AttributeSet;
@@ -258,7 +257,7 @@
}
onTimeChanged();
-
+
invalidate();
}
};
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 1a1680a..68e6809 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -16,10 +16,6 @@
*/
package android.widget;
-import android.annotation.SystemApi;
-import android.os.UserHandle;
-import com.android.internal.R;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -31,6 +27,7 @@
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
+import android.os.UserHandle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -39,6 +36,8 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.R;
+
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
@@ -57,7 +56,7 @@
* extended information consisting of all groups and permissions.
* To use this view define a LinearLayout or any ViewGroup and add this
* view by instantiating AppSecurityPermissions and invoking getPermissionsView.
- *
+ *
* {@hide}
*/
public class AppSecurityPermissions {
@@ -324,7 +323,7 @@
return getPermissionItemViewOld(context, inflater, grpName,
description, dangerous, icon);
}
-
+
private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
if(sharedPkgList == null || (sharedPkgList.length == 0)) {
@@ -334,7 +333,7 @@
getPermissionsForPackage(sharedPkg, permSet);
}
}
-
+
private void getPermissionsForPackage(String packageName, Set<MyPermissionInfo> permSet) {
try {
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
@@ -417,7 +416,7 @@
}
}
}
-
+
public int getPermissionCount() {
return getPermissionCount(WHICH_ALL);
}
@@ -570,7 +569,7 @@
}
return false;
}
-
+
private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> {
private final Collator sCollator = Collator.getInstance();
@Override
@@ -578,7 +577,7 @@
return sCollator.compare(a.mLabel, b.mLabel);
}
}
-
+
private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> {
private final Collator sCollator = Collator.getInstance();
PermissionInfoComparator() {
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 6a4e36a..49741d4 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -39,7 +39,9 @@
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
+
import com.android.internal.R;
+
import java.lang.ref.WeakReference;
/**
@@ -362,22 +364,22 @@
* <p>Returns the current width for the auto-complete drop down list. This can
* be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
- *
+ *
* @return the width for the drop down list
- *
+ *
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public int getDropDownWidth() {
return mPopup.getWidth();
}
-
+
/**
* <p>Sets the current width for the auto-complete drop down list. This can
* be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
- *
+ *
* @param width the width to use
- *
+ *
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public void setDropDownWidth(int width) {
@@ -411,68 +413,68 @@
public void setDropDownHeight(int height) {
mPopup.setHeight(height);
}
-
+
/**
* <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
- *
+ *
* @return the view's id, or {@link View#NO_ID} if none specified
- *
+ *
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
*/
public int getDropDownAnchor() {
return mDropDownAnchorId;
}
-
+
/**
* <p>Sets the view to which the auto-complete drop down list should anchor. The view
* corresponding to this id will not be loaded until the next time it is needed to avoid
* loading a view which is not yet instantiated.</p>
- *
+ *
* @param id the id to anchor the drop down list view to
- *
- * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
+ *
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
*/
public void setDropDownAnchor(int id) {
mDropDownAnchorId = id;
mPopup.setAnchorView(null);
}
-
+
/**
* <p>Gets the background of the auto-complete drop-down list.</p>
- *
+ *
* @return the background drawable
- *
+ *
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
public Drawable getDropDownBackground() {
return mPopup.getBackground();
}
-
+
/**
* <p>Sets the background of the auto-complete drop-down list.</p>
- *
+ *
* @param d the drawable to set as the background
- *
+ *
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
public void setDropDownBackgroundDrawable(Drawable d) {
mPopup.setBackgroundDrawable(d);
}
-
+
/**
* <p>Sets the background of the auto-complete drop-down list.</p>
- *
+ *
* @param id the id of the drawable to set as the background
- *
+ *
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
public void setDropDownBackgroundResource(@DrawableRes int id) {
mPopup.setBackgroundDrawable(getContext().getDrawable(id));
}
-
+
/**
* <p>Sets the vertical offset used for the auto-complete drop-down list.</p>
- *
+ *
* @param offset the vertical offset
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
@@ -480,10 +482,10 @@
public void setDropDownVerticalOffset(int offset) {
mPopup.setVerticalOffset(offset);
}
-
+
/**
* <p>Gets the vertical offset used for the auto-complete drop-down list.</p>
- *
+ *
* @return the vertical offset
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
@@ -491,10 +493,10 @@
public int getDropDownVerticalOffset() {
return mPopup.getVerticalOffset();
}
-
+
/**
* <p>Sets the horizontal offset used for the auto-complete drop-down list.</p>
- *
+ *
* @param offset the horizontal offset
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
@@ -502,10 +504,10 @@
public void setDropDownHorizontalOffset(int offset) {
mPopup.setHorizontalOffset(offset);
}
-
+
/**
* <p>Gets the horizontal offset used for the auto-complete drop-down list.</p>
- *
+ *
* @return the horizontal offset
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
@@ -567,10 +569,10 @@
public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
mPopup.setDropDownAlwaysVisible(dropDownAlwaysVisible);
}
-
+
/**
* Checks whether the drop-down is dismissed when a suggestion is clicked.
- *
+ *
* @hide Pending API council approval
*/
public boolean isDropDownDismissedOnCompletion() {
@@ -578,17 +580,17 @@
}
/**
- * Sets whether the drop-down is dismissed when a suggestion is clicked. This is
+ * Sets whether the drop-down is dismissed when a suggestion is clicked. This is
* true by default.
- *
+ *
* @param dropDownDismissedOnCompletion Whether to dismiss the drop-down.
- *
+ *
* @hide Pending API council approval
*/
public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) {
mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion;
}
-
+
/**
* <p>Returns the number of characters the user must type before the drop
* down list is shown.</p>
@@ -720,13 +722,13 @@
/**
* <p>Changes the list of data used for auto completion. The provided list
* must be a filterable list adapter.</p>
- *
+ *
* <p>The caller is still responsible for managing any resources used by the adapter.
* Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified.
* A common case is the use of {@link android.widget.CursorAdapter}, which
* contains a {@link android.database.Cursor} that must be closed. This can be done
- * automatically (see
- * {@link android.app.Activity#startManagingCursor(android.database.Cursor)
+ * automatically (see
+ * {@link android.app.Activity#startManagingCursor(android.database.Cursor)
* startManagingCursor()}),
* or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p>
*
@@ -811,7 +813,7 @@
if (mPopup.onKeyDown(keyCode, event)) {
return true;
}
-
+
if (!isPopupShowing()) {
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -924,18 +926,18 @@
protected CharSequence convertSelectionToString(Object selectedItem) {
return mFilter.convertResultToString(selectedItem);
}
-
+
/**
- * <p>Clear the list selection. This may only be temporary, as user input will often bring
+ * <p>Clear the list selection. This may only be temporary, as user input will often bring
* it back.
*/
public void clearListSelection() {
mPopup.clearListSelection();
}
-
+
/**
* Set the position of the dropdown view selection.
- *
+ *
* @param position The position to move the selector to.
*/
public void setListSelection(int position) {
@@ -943,13 +945,13 @@
}
/**
- * Get the position of the dropdown view selection, if there is one. Returns
+ * Get the position of the dropdown view selection, if there is one. Returns
* {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if
* there is no selection.
- *
- * @return the position of the current selection, if there is one, or
+ *
+ * @return the position of the current selection, if there is one, or
* {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if not.
- *
+ *
* @see ListView#getSelectedItemPosition()
*/
public int getListSelection() {
@@ -1020,7 +1022,7 @@
dismissDropDown();
}
}
-
+
/**
* Identifies whether the view is currently performing a text completion, so subclasses
* can decide whether to respond to text changed events.
@@ -1172,7 +1174,7 @@
public void showDropDownAfterLayout() {
mPopup.postShow();
}
-
+
/**
* Ensures that the drop down is not obscuring the IME.
* @param visible whether the ime should be in front. If false, the ime is pushed to
@@ -1220,7 +1222,7 @@
* Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
* false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
* ignore outside touch even when the drop down is not set to always visible.
- *
+ *
* @hide used only by SearchDialog
*/
public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
@@ -1245,7 +1247,7 @@
realCount++;
}
}
-
+
if (realCount != count) {
CompletionInfo[] tmp = new CompletionInfo[realCount];
System.arraycopy(completions, 0, tmp, 0, realCount);
@@ -1340,7 +1342,7 @@
*/
CharSequence fixText(CharSequence invalidText);
}
-
+
/**
* Listener to respond to the AutoCompleteTextView's completion list being dismissed.
* @see AutoCompleteTextView#setOnDismissListener(OnDismissListener)
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 6ef1576..db50e34 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.AttrRes;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
@@ -36,6 +34,8 @@
import android.util.AttributeSet;
import android.util.Log;
+import com.android.internal.R;
+
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
diff --git a/core/java/android/widget/CalendarViewLegacyDelegate.java b/core/java/android/widget/CalendarViewLegacyDelegate.java
index bcda83f..557d411 100644
--- a/core/java/android/widget/CalendarViewLegacyDelegate.java
+++ b/core/java/android/widget/CalendarViewLegacyDelegate.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.app.Service;
import android.content.Context;
import android.content.res.Configuration;
@@ -38,10 +36,12 @@
import android.view.View;
import android.view.ViewGroup;
-import java.util.Locale;
+import com.android.internal.R;
import libcore.icu.LocaleData;
+import java.util.Locale;
+
/**
* A delegate implementing the legacy CalendarView
*/
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 15bbdd2..046f75f 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.util.AttributeSet;
-
/**
* <p>
* A checkbox is a specific type of two-states button that can be either
@@ -44,12 +43,12 @@
*
* <p>See the <a href="{@docRoot}guide/topics/ui/controls/checkbox.html">Checkboxes</a>
* guide.</p>
- *
- * <p><strong>XML attributes</strong></p>
+ *
+ * <p><strong>XML attributes</strong></p>
* <p>
- * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
- * {@link android.R.styleable#Button Button Attributes},
- * {@link android.R.styleable#TextView TextView Attributes},
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
+ * {@link android.R.styleable#Button Button Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
* {@link android.R.styleable#View View Attributes}
* </p>
*/
@@ -57,7 +56,7 @@
public CheckBox(Context context) {
this(context, null);
}
-
+
public CheckBox(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.checkboxStyle);
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 21595d15..92bfd56 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -16,11 +16,8 @@
package android.widget;
-import android.annotation.NonNull;
-import android.view.ViewHierarchyEncoder;
-import com.android.internal.R;
-
import android.annotation.DrawableRes;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -34,9 +31,12 @@
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
/**
* An extension to {@link TextView} that supports the {@link Checkable}
* interface and displays.
@@ -394,7 +394,7 @@
y = (getHeight() - height) / 2;
break;
}
-
+
final boolean checkMarkAtStart = isCheckMarkAtStart();
final int width = getWidth();
final int top = y;
@@ -417,7 +417,7 @@
}
}
}
-
+
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index adcb012..b9224f3 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -24,7 +24,6 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
@@ -80,7 +79,7 @@
private OnChronometerTickListener mOnChronometerTickListener;
private StringBuilder mRecycle = new StringBuilder(8);
private boolean mCountDown;
-
+
/**
* Initialize this Chronometer object.
* Sets the base to the current time.
@@ -191,7 +190,7 @@
/**
* Sets the listener to be called when the chronometer changes.
- *
+ *
* @param listener The listener.
*/
public void setOnChronometerTickListener(OnChronometerTickListener listener) {
@@ -209,10 +208,10 @@
/**
* Start counting up. This does not affect the base as set from {@link #setBase}, just
* the view display.
- *
- * Chronometer works by regularly scheduling messages to the handler, even when the
- * Widget is not visible. To make sure resource leaks do not occur, the user should
- * make sure that each start() call has a reciprocal call to {@link #stop}.
+ *
+ * Chronometer works by regularly scheduling messages to the handler, even when the
+ * Widget is not visible. To make sure resource leaks do not occur, the user should
+ * make sure that each start() call has a reciprocal call to {@link #stop}.
*/
public void start() {
mStarted = true;
@@ -222,9 +221,9 @@
/**
* Stop counting up. This does not affect the base as set from {@link #setBase}, just
* the view display.
- *
+ *
* This stops the messages to the handler, effectively releasing resources that would
- * be held as the chronometer is running, via {@link #start}.
+ * be held as the chronometer is running, via {@link #start}.
*/
public void stop() {
mStarted = false;
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0357834..dd63fef 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -19,14 +19,11 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.graphics.PorterDuff;
-import android.view.ViewHierarchyEncoder;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,9 +31,12 @@
import android.view.Gravity;
import android.view.SoundEffectConstants;
import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
/**
* <p>
* A button with two states, checked and unchecked. When the button is pressed
@@ -159,7 +159,7 @@
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
- mBroadcasting = false;
+ mBroadcasting = false;
}
}
@@ -492,7 +492,7 @@
SavedState(Parcelable superState) {
super(superState);
}
-
+
/**
* Constructor called from {@link #CREATOR}
*/
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 80f25d4..517e20cd 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -35,6 +33,8 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 1bf0c87..f712685 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -40,6 +38,8 @@
import android.widget.DayPickerView.OnDaySelectedListener;
import android.widget.YearPickerView.OnYearSelectedListener;
+import com.android.internal.R;
+
import java.util.Locale;
/**
diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java
index 9ecf8a5..702b2a5 100644
--- a/core/java/android/widget/DatePickerSpinnerDelegate.java
+++ b/core/java/android/widget/DatePickerSpinnerDelegate.java
@@ -33,14 +33,14 @@
import android.widget.DatePicker.AbstractDatePickerDelegate;
import android.widget.NumberPicker.OnValueChangeListener;
+import libcore.icu.ICU;
+
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;
-import libcore.icu.ICU;
-
/**
* A delegate implementing the basic DatePicker
*/
diff --git a/core/java/android/widget/DayPickerPagerAdapter.java b/core/java/android/widget/DayPickerPagerAdapter.java
index f748d3a..8d5bf8f 100644
--- a/core/java/android/widget/DayPickerPagerAdapter.java
+++ b/core/java/android/widget/DayPickerPagerAdapter.java
@@ -16,9 +16,6 @@
package android.widget;
-import android.graphics.Rect;
-import com.android.internal.widget.PagerAdapter;
-
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
@@ -26,6 +23,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.icu.util.Calendar;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -33,6 +31,8 @@
import android.view.ViewGroup;
import android.widget.SimpleMonthView.OnDayClickListener;
+import com.android.internal.widget.PagerAdapter;
+
/**
* An adapter for a list of {@link android.widget.SimpleMonthView} items.
*/
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 0c02a2b..919c1e2 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -18,15 +18,11 @@
import static android.os.Build.VERSION_CODES.N_MR1;
-import android.graphics.Rect;
-import com.android.internal.R;
-import com.android.internal.widget.ViewPager;
-import com.android.internal.widget.ViewPager.OnPageChangeListener;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.icu.util.Calendar;
import android.util.AttributeSet;
import android.util.MathUtils;
@@ -35,10 +31,14 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
-import java.util.Locale;
+import com.android.internal.R;
+import com.android.internal.widget.ViewPager;
+import com.android.internal.widget.ViewPager.OnPageChangeListener;
import libcore.icu.LocaleData;
+import java.util.Locale;
+
class DayPickerView extends ViewGroup {
private static final int DEFAULT_LAYOUT = R.layout.day_picker_content_material;
private static final int DEFAULT_START_YEAR = 1900;
diff --git a/core/java/android/widget/DayPickerViewPager.java b/core/java/android/widget/DayPickerViewPager.java
index 5f0ae29..94022ae 100644
--- a/core/java/android/widget/DayPickerViewPager.java
+++ b/core/java/android/widget/DayPickerViewPager.java
@@ -16,17 +16,12 @@
package android.widget;
-import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import com.android.internal.util.Predicate;
-import com.android.internal.widget.PagerAdapter;
import com.android.internal.widget.ViewPager;
import java.util.ArrayList;
diff --git a/core/java/android/widget/DialerFilter.java b/core/java/android/widget/DialerFilter.java
index e37e3ba7..8f9c96c 100644
--- a/core/java/android/widget/DialerFilter.java
+++ b/core/java/android/widget/DialerFilter.java
@@ -17,7 +17,7 @@
package android.widget;
import android.content.Context;
-import android.view.KeyEvent;
+import android.graphics.Rect;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
@@ -28,8 +28,8 @@
import android.text.method.KeyListener;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
+import android.view.KeyEvent;
import android.view.View;
-import android.graphics.Rect;
/**
* This widget is a layout that contains several specifically-named child views that
@@ -169,7 +169,7 @@
// Only check to see if the digit is valid if the key is a printing key
// in the TextKeyListener. This prevents us from hiding the digits
// line when keys like UP and DOWN are hit.
- // XXX note that KEYCODE_TAB is special-cased here for
+ // XXX note that KEYCODE_TAB is special-cased here for
// devices that share tab and 0 on a single key.
boolean isPrint = event.isPrintingKey();
if (isPrint || keyCode == KeyEvent.KEYCODE_SPACE
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
index 1df1643..c503ef2 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -23,6 +23,7 @@
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
+
import java.util.Calendar;
/**
diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java
index 69e4218..e9c4728 100644
--- a/core/java/android/widget/DropDownListView.java
+++ b/core/java/android/widget/DropDownListView.java
@@ -17,13 +17,13 @@
package android.widget;
-import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
-
import android.annotation.NonNull;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
+import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
+
/**
* Wrapper class for a ListView. This wrapper can hijack the focus to
* make sure the list uses the appropriate drawables and states when
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 9019f31..98d8a13 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -17,14 +17,13 @@
package android.widget;
import android.annotation.ColorInt;
+import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-
-import android.content.Context;
-import android.graphics.Canvas;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -103,7 +102,7 @@
private int mState = STATE_IDLE;
private float mPullDistance;
-
+
private final Rect mBounds = new Rect();
private final Paint mPaint = new Paint();
private float mRadius;
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 24d8618..a8d3984 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -17,7 +17,6 @@
package android.widget;
import android.content.Context;
-import android.os.Bundle;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
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/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index fac36c5..8d9848d 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -16,7 +16,7 @@
package android.widget;
-import com.android.internal.R;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import android.content.Context;
import android.content.res.TypedArray;
@@ -27,14 +27,14 @@
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.SoundEffectConstants;
import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
import android.widget.ExpandableListConnector.PositionMetadata;
-import java.util.ArrayList;
+import com.android.internal.R;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import java.util.ArrayList;
/**
* A view that shows items in a vertically scrolling two-level list. This
@@ -68,7 +68,7 @@
* wrap_content since it also can be any length. However, you can use
* wrap_content if the ExpandableListView parent has a specific size, such as
* 100 pixels.
- *
+ *
* @attr ref android.R.styleable#ExpandableListView_groupIndicator
* @attr ref android.R.styleable#ExpandableListView_indicatorLeft
* @attr ref android.R.styleable#ExpandableListView_indicatorRight
@@ -87,7 +87,7 @@
* The packed position represents a group.
*/
public static final int PACKED_POSITION_TYPE_GROUP = 0;
-
+
/**
* The packed position represents a child.
*/
@@ -97,14 +97,14 @@
* The packed position represents a neither/null/no preference.
*/
public static final int PACKED_POSITION_TYPE_NULL = 2;
-
+
/**
* The value for a packed position that represents neither/null/no
* preference. This value is not otherwise possible since a group type
* (first bit 0) should not have a child position filled.
*/
public static final long PACKED_POSITION_VALUE_NULL = 0x00000000FFFFFFFFL;
-
+
/** The mask (in packed position representation) for the child */
private static final long PACKED_POSITION_MASK_CHILD = 0x00000000FFFFFFFFL;
@@ -125,13 +125,13 @@
/** The mask (in integer group position representation) for the group */
private static final long PACKED_POSITION_INT_MASK_GROUP = 0x7FFFFFFF;
-
+
/** Serves as the glue/translator between a ListView and an ExpandableListView */
private ExpandableListConnector mConnector;
-
- /** Gives us Views through group+child positions */
+
+ /** Gives us Views through group+child positions */
private ExpandableListAdapter mAdapter;
-
+
/** Left bound for drawing the indicator. */
private int mIndicatorLeft;
@@ -210,7 +210,7 @@
/** State indicating the child is the last within its group. */
private static final int[] CHILD_LAST_STATE_SET =
{R.attr.state_last};
-
+
/** Drawable to be used as a divider when it is adjacent to any children */
private Drawable mChildDivider;
@@ -367,16 +367,16 @@
}
final int headerViewsCount = getHeaderViewsCount();
-
+
final int lastChildFlPos = mItemCount - getFooterViewsCount() - headerViewsCount - 1;
- final int myB = mBottom;
-
+ final int myB = mBottom;
+
PositionMetadata pos;
View item;
- Drawable indicator;
+ Drawable indicator;
int t, b;
-
+
// Start at a value that is neither child nor group
int lastItemType = ~(ExpandableListPosition.CHILD | ExpandableListPosition.GROUP);
@@ -385,7 +385,7 @@
// The "child" mentioned in the following two lines is this
// View's child, not referring to an expandable list's
// notion of a child (as opposed to a group)
- final int childCount = getChildCount();
+ final int childCount = getChildCount();
for (int i = 0, childFlPos = mFirstPosition - headerViewsCount; i < childCount;
i++, childFlPos++) {
@@ -396,11 +396,11 @@
// This child is footer, so are all subsequent children
break;
}
-
+
item = getChildAt(i);
t = item.getTop();
b = item.getBottom();
-
+
// This item isn't on the screen
if ((b < 0) || (t > myB)) continue;
@@ -435,7 +435,7 @@
indicatorRect.right += mPaddingLeft;
}
- lastItemType = pos.position.type;
+ lastItemType = pos.position.type;
}
if (indicatorRect.left != indicatorRect.right) {
@@ -448,7 +448,7 @@
indicatorRect.top = t;
indicatorRect.bottom = b;// + mDividerHeight;
}
-
+
// Get the indicator (with its state set to the item's state)
indicator = getIndicator(pos);
if (indicator != null) {
@@ -468,24 +468,24 @@
/**
* Gets the indicator for the item at the given position. If the indicator
* is stateful, the state will be given to the indicator.
- *
+ *
* @param pos The flat list position of the item whose indicator
* should be returned.
* @return The indicator in the proper state.
*/
private Drawable getIndicator(PositionMetadata pos) {
Drawable indicator;
-
+
if (pos.position.type == ExpandableListPosition.GROUP) {
indicator = mGroupIndicator;
-
+
if (indicator != null && indicator.isStateful()) {
// Empty check based on availability of data. If the groupMetadata isn't null,
// we do a check on it. Otherwise, the group is collapsed so we consider it
// empty for performance reasons.
boolean isEmpty = (pos.groupMetadata == null) ||
(pos.groupMetadata.lastChildFlPos == pos.groupMetadata.flPos);
-
+
final int stateSetIndex =
(pos.isExpanded() ? 1 : 0) | // Expanded?
(isEmpty ? 2 : 0); // Empty?
@@ -493,7 +493,7 @@
}
} else {
indicator = mChildIndicator;
-
+
if (indicator != null && indicator.isStateful()) {
// No need for a state sets array for the child since it only has two states
final int stateSet[] = pos.position.flatListPos == pos.groupMetadata.lastChildFlPos
@@ -502,15 +502,15 @@
indicator.setState(stateSet);
}
}
-
+
return indicator;
}
-
+
/**
* Sets the drawable that will be drawn adjacent to every child in the list. This will
* be drawn using the same height as the normal divider ({@link #setDivider(Drawable)}) or
* if it does not have an intrinsic height, the height set by {@link #setDividerHeight(int)}.
- *
+ *
* @param childDivider The drawable to use.
*/
public void setChildDivider(Drawable childDivider) {
@@ -520,7 +520,7 @@
@Override
void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
int flatListPosition = childIndex + mFirstPosition;
-
+
// Only proceed as possible child if the divider isn't above all items (if it is above
// all items, then the item below it has to be a group)
if (flatListPosition >= 0) {
@@ -538,7 +538,7 @@
}
pos.recycle();
}
-
+
// Otherwise draw the default divider
super.drawDivider(canvas, bounds, flatListPosition);
}
@@ -589,18 +589,18 @@
public void setAdapter(ExpandableListAdapter adapter) {
// Set member variable
mAdapter = adapter;
-
+
if (adapter != null) {
// Create the connector
mConnector = new ExpandableListConnector(adapter);
} else {
mConnector = null;
}
-
+
// Link the ListView (superclass) to the expandable list data through the connector
super.setAdapter(mConnector);
}
-
+
/**
* Gets the adapter that provides data to this view.
* @return The adapter that provides data to this view.
@@ -608,7 +608,7 @@
public ExpandableListAdapter getExpandableListAdapter() {
return mAdapter;
}
-
+
/**
* @param position An absolute (including header and footer) flat list position.
* @return true if the position corresponds to a header or a footer item.
@@ -621,7 +621,7 @@
/**
* Converts an absolute item flat position into a group/child flat position, shifting according
* to the number of header items.
- *
+ *
* @param flatListPosition The absolute flat position
* @return A group/child flat position as expected by the connector.
*/
@@ -632,7 +632,7 @@
/**
* Converts a group/child flat position into an absolute flat position, that takes into account
* the possible headers.
- *
+ *
* @param flatListPosition The child/group flat position
* @return An absolute flat position.
*/
@@ -647,25 +647,25 @@
// Clicked on a header/footer, so ignore pass it on to super
return super.performItemClick(v, position, id);
}
-
+
// Internally handle the item click
final int adjustedPosition = getFlatPositionForConnector(position);
return handleItemClick(v, adjustedPosition, id);
}
-
+
/**
* This will either expand/collapse groups (if a group was clicked) or pass
* on the click to the proper child (if a child was clicked)
- *
+ *
* @param position The flat list position. This has already been factored to
* remove the header/footer.
* @param id The ListAdapter ID, not the group or child ID.
*/
boolean handleItemClick(View v, int position, long id) {
final PositionMetadata posMetadata = mConnector.getUnflattenedPos(position);
-
+
id = getChildOrGroupId(posMetadata.position);
-
+
boolean returnValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
/* It's a group, so handle collapsing/expanding */
@@ -697,11 +697,11 @@
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
}
-
+
final int groupPos = posMetadata.position.groupPos;
final int groupFlatPos = posMetadata.position.flatListPos;
- final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
+ final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
shiftedGroupPosition);
}
@@ -764,17 +764,17 @@
return retValue;
}
-
+
/**
* Collapse a group in the grouped list view
- *
+ *
* @param groupPos position of the group to collapse
* @return True if the group was collapsed, false otherwise (if the group
* was already collapsed, this will return false)
*/
public boolean collapseGroup(int groupPos) {
boolean retValue = mConnector.collapseGroup(groupPos);
-
+
if (mOnGroupCollapseListener != null) {
mOnGroupCollapseListener.onGroupCollapse(groupPos);
}
@@ -787,14 +787,14 @@
/**
* Callback method to be invoked when a group in this expandable list has
* been collapsed.
- *
+ *
* @param groupPosition The group position that was collapsed
*/
void onGroupCollapse(int groupPosition);
}
-
+
private OnGroupCollapseListener mOnGroupCollapseListener;
-
+
public void setOnGroupCollapseListener(
OnGroupCollapseListener onGroupCollapseListener) {
mOnGroupCollapseListener = onGroupCollapseListener;
@@ -805,14 +805,14 @@
/**
* Callback method to be invoked when a group in this expandable list has
* been expanded.
- *
+ *
* @param groupPosition The group position that was expanded
*/
void onGroupExpand(int groupPosition);
}
-
+
private OnGroupExpandListener mOnGroupExpandListener;
-
+
public void setOnGroupExpandListener(
OnGroupExpandListener onGroupExpandListener) {
mOnGroupExpandListener = onGroupExpandListener;
@@ -826,7 +826,7 @@
/**
* Callback method to be invoked when a group in this expandable list has
* been clicked.
- *
+ *
* @param parent The ExpandableListConnector where the click happened
* @param v The view within the expandable list/ListView that was clicked
* @param groupPosition The group position that was clicked
@@ -836,13 +836,13 @@
boolean onGroupClick(ExpandableListView parent, View v, int groupPosition,
long id);
}
-
+
private OnGroupClickListener mOnGroupClickListener;
public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
mOnGroupClickListener = onGroupClickListener;
}
-
+
/**
* Interface definition for a callback to be invoked when a child in this
* expandable list has been clicked.
@@ -851,7 +851,7 @@
/**
* Callback method to be invoked when a child in this expandable list has
* been clicked.
- *
+ *
* @param parent The ExpandableListView where the click happened
* @param v The view within the expandable list/ListView that was clicked
* @param groupPosition The group position that contains the child that
@@ -863,13 +863,13 @@
boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id);
}
-
+
private OnChildClickListener mOnChildClickListener;
public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
mOnChildClickListener = onChildClickListener;
}
-
+
/**
* Converts a flat list position (the raw position of an item (child or group)
* in the list) to a group and/or child position (represented in a
@@ -878,7 +878,7 @@
* {@link ExpandableListView#getPackedPositionType} ,
* {@link ExpandableListView#getPackedPositionChild},
* {@link ExpandableListView#getPackedPositionGroup} to unpack.
- *
+ *
* @param flatListPosition The flat list position to be converted.
* @return The group and/or child position for the given flat list position
* in packed position representation. #PACKED_POSITION_VALUE_NULL if
@@ -895,12 +895,12 @@
pm.recycle();
return packedPos;
}
-
+
/**
* Converts a group and/or child position to a flat list position. This is
* useful in situations where the caller needs to use the underlying
* {@link ListView}'s methods.
- *
+ *
* @param packedPosition The group and/or child positions to be converted in
* packed position representation. Use
* {@link #getPackedPositionForChild(int, int)} or
@@ -920,7 +920,7 @@
/**
* Gets the position of the currently selected group or child (along with
* its type). Can return {@link #PACKED_POSITION_VALUE_NULL} if no selection.
- *
+ *
* @return A packed position containing the currently selected group or
* child's position and type. #PACKED_POSITION_VALUE_NULL if no selection
* or if selection is on a header or a footer item.
@@ -931,11 +931,11 @@
// The case where there is no selection (selectedPos == -1) is also handled here.
return getExpandableListPosition(selectedPos);
}
-
+
/**
* Gets the ID of the currently selected group or child. Can return -1 if no
* selection.
- *
+ *
* @return The ID of the currently selected group or child. -1 if no
* selection.
*/
@@ -944,7 +944,7 @@
if (packedPos == PACKED_POSITION_VALUE_NULL) return -1;
int groupPos = getPackedPositionGroup(packedPos);
-
+
if (getPackedPositionType(packedPos) == PACKED_POSITION_TYPE_GROUP) {
// It's a group
return mAdapter.getGroupId(groupPos);
@@ -953,7 +953,7 @@
return mAdapter.getChildId(groupPos, getPackedPositionChild(packedPos));
}
}
-
+
/**
* Sets the selection to the specified group.
* @param groupPosition The position of the group that should be selected.
@@ -967,12 +967,12 @@
super.setSelection(absoluteFlatPosition);
pm.recycle();
}
-
+
/**
* Sets the selection to the specified child. If the child is in a collapsed
* group, the group will only be expanded and child subsequently selected if
* shouldExpandGroup is set to true, otherwise the method will return false.
- *
+ *
* @param groupPosition The position of the group that contains the child.
* @param childPosition The position of the child within the group.
* @param shouldExpandGroup Whether the child's group should be expanded if
@@ -981,48 +981,48 @@
*/
public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
ExpandableListPosition elChildPos = ExpandableListPosition.obtainChildPosition(
- groupPosition, childPosition);
+ groupPosition, childPosition);
PositionMetadata flatChildPos = mConnector.getFlattenedPos(elChildPos);
-
+
if (flatChildPos == null) {
// The child's group isn't expanded
-
+
// Shouldn't expand the group, so return false for we didn't set the selection
- if (!shouldExpandGroup) return false;
+ if (!shouldExpandGroup) return false;
expandGroup(groupPosition);
-
+
flatChildPos = mConnector.getFlattenedPos(elChildPos);
-
+
// Sanity check
if (flatChildPos == null) {
throw new IllegalStateException("Could not find child");
}
}
-
+
int absoluteFlatPosition = getAbsoluteFlatPosition(flatChildPos.position.flatListPos);
super.setSelection(absoluteFlatPosition);
-
+
elChildPos.recycle();
flatChildPos.recycle();
-
+
return true;
}
/**
* Whether the given group is currently expanded.
- *
+ *
* @param groupPosition The group to check.
* @return Whether the group is currently expanded.
*/
public boolean isGroupExpanded(int groupPosition) {
return mConnector.isGroupExpanded(groupPosition);
}
-
+
/**
* Gets the type of a packed position. See
* {@link #getPackedPositionForChild(int, int)}.
- *
+ *
* @param packedPosition The packed position for which to return the type.
* @return The type of the position contained within the packed position,
* either {@link #PACKED_POSITION_TYPE_CHILD}, {@link #PACKED_POSITION_TYPE_GROUP}, or
@@ -1032,7 +1032,7 @@
if (packedPosition == PACKED_POSITION_VALUE_NULL) {
return PACKED_POSITION_TYPE_NULL;
}
-
+
return (packedPosition & PACKED_POSITION_MASK_TYPE) == PACKED_POSITION_MASK_TYPE
? PACKED_POSITION_TYPE_CHILD
: PACKED_POSITION_TYPE_GROUP;
@@ -1041,7 +1041,7 @@
/**
* Gets the group position from a packed position. See
* {@link #getPackedPositionForChild(int, int)}.
- *
+ *
* @param packedPosition The packed position from which the group position
* will be returned.
* @return The group position portion of the packed position. If this does
@@ -1050,7 +1050,7 @@
public static int getPackedPositionGroup(long packedPosition) {
// Null
if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
-
+
return (int) ((packedPosition & PACKED_POSITION_MASK_GROUP) >> PACKED_POSITION_SHIFT_GROUP);
}
@@ -1060,7 +1060,7 @@
* To get the group that this child belongs to, use
* {@link #getPackedPositionGroup(long)}. See
* {@link #getPackedPositionForChild(int, int)}.
- *
+ *
* @param packedPosition The packed position from which the child position
* will be returned.
* @return The child position portion of the packed position. If this does
@@ -1069,7 +1069,7 @@
public static int getPackedPositionChild(long packedPosition) {
// Null
if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
-
+
// Group since a group type clears this bit
if ((packedPosition & PACKED_POSITION_MASK_TYPE) != PACKED_POSITION_MASK_TYPE) return -1;
@@ -1087,7 +1087,7 @@
* {@link #getPackedPositionChild(long)},
* {@link #getPackedPositionGroup(long)}, and
* {@link #getPackedPositionType(long)}.
- *
+ *
* @param groupPosition The child's parent group's position.
* @param childPosition The child position within the group.
* @return The packed position representation of the child (and parent group).
@@ -1096,20 +1096,20 @@
return (((long)PACKED_POSITION_TYPE_CHILD) << PACKED_POSITION_SHIFT_TYPE)
| ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
<< PACKED_POSITION_SHIFT_GROUP)
- | (childPosition & PACKED_POSITION_INT_MASK_CHILD);
+ | (childPosition & PACKED_POSITION_INT_MASK_CHILD);
}
/**
* Returns the packed position representation of a group's position. See
* {@link #getPackedPositionForChild(int, int)}.
- *
+ *
* @param groupPosition The child's parent group's position.
* @return The packed position representation of the group.
*/
public static long getPackedPositionForGroup(int groupPosition) {
// No need to OR a type in because PACKED_POSITION_GROUP == 0
return ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
- << PACKED_POSITION_SHIFT_GROUP);
+ << PACKED_POSITION_SHIFT_GROUP);
}
@Override
@@ -1122,12 +1122,12 @@
final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
ExpandableListPosition pos = pm.position;
-
+
id = getChildOrGroupId(pos);
long packedPosition = pos.getPackedPosition();
pm.recycle();
-
+
return new ExpandableListContextMenuInfo(view, packedPosition, id);
}
@@ -1135,7 +1135,7 @@
* Gets the ID of the group or child at the given <code>position</code>.
* This is useful since there is no ListAdapter ID -> ExpandableListAdapter
* ID conversion mechanism (in some cases, it isn't possible).
- *
+ *
* @param position The position of the child or group whose ID should be
* returned.
*/
@@ -1146,10 +1146,10 @@
return mAdapter.getGroupId(position.groupPos);
}
}
-
+
/**
* Sets the indicator to be drawn next to a child.
- *
+ *
* @param childIndicator The drawable to be used as an indicator. If the
* child is the last child for a group, the state
* {@link android.R.attr#state_last} will be set.
@@ -1157,7 +1157,7 @@
public void setChildIndicator(Drawable childIndicator) {
mChildIndicator = childIndicator;
}
-
+
/**
* Sets the drawing bounds for the child indicator. For either, you can
* specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
@@ -1194,7 +1194,7 @@
/**
* Sets the indicator to be drawn next to a group.
- *
+ *
* @param groupIndicator The drawable to be used as an indicator. If the
* group is empty, the state {@link android.R.attr#state_empty} will be
* set. If the group is expanded, the state
@@ -1211,8 +1211,8 @@
* Sets the drawing bounds for the indicators (at minimum, the group indicator
* is affected by this; the child indicator is affected by this if the
* child indicator bounds are set to inherit).
- *
- * @see #setChildIndicatorBounds(int, int)
+ *
+ * @see #setChildIndicatorBounds(int, int)
* @param left The left position (relative to the left bounds of this View)
* to start drawing the indicator.
* @param right The right position (relative to the left bounds of this
@@ -1248,13 +1248,13 @@
* callback when a context menu is brought up for this AdapterView.
*/
public static class ExpandableListContextMenuInfo implements ContextMenu.ContextMenuInfo {
-
+
public ExpandableListContextMenuInfo(View targetView, long packedPosition, long id) {
this.targetView = targetView;
this.packedPosition = packedPosition;
this.id = id;
}
-
+
/**
* The view for which the context menu is being displayed. This
* will be one of the children Views of this {@link ExpandableListView}.
@@ -1276,10 +1276,10 @@
*/
public long id;
}
-
+
static class SavedState extends BaseSavedState {
ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList;
-
+
/**
* Constructor called from {@link ExpandableListView#onSaveInstanceState()}
*/
@@ -1333,7 +1333,7 @@
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
-
+
if (mConnector != null && ss.expandedGroupMetadataList != null) {
mConnector.setExpandedGroupMetadataList(ss.expandedGroupMetadataList);
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index b8c74d8..dc8ee01 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +32,8 @@
import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.R;
+
import java.util.ArrayList;
/**
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index af2852c..1c15c7a 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -16,6 +16,20 @@
package android.widget;
+import static android.view.Gravity.AXIS_PULL_AFTER;
+import static android.view.Gravity.AXIS_PULL_BEFORE;
+import static android.view.Gravity.AXIS_SPECIFIED;
+import static android.view.Gravity.AXIS_X_SHIFT;
+import static android.view.Gravity.AXIS_Y_SHIFT;
+import static android.view.Gravity.HORIZONTAL_GRAVITY_MASK;
+import static android.view.Gravity.RELATIVE_LAYOUT_DIRECTION;
+import static android.view.Gravity.VERTICAL_GRAVITY_MASK;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.TypedArray;
@@ -32,6 +46,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews.RemoteView;
+
import com.android.internal.R;
import java.lang.annotation.Retention;
@@ -43,12 +58,6 @@
import java.util.List;
import java.util.Map;
-import static android.view.Gravity.*;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-
/**
* A layout that places its children in a rectangular <em>grid</em>.
* <p>
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 2b822fc..82071d7 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -36,9 +36,9 @@
import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
import android.view.animation.GridLayoutAnimationController;
import android.widget.RemoteViews.RemoteView;
@@ -54,7 +54,7 @@
*
* <p>See the <a href="{@docRoot}guide/topics/ui/layout/gridview.html">Grid
* View</a> guide.</p>
- *
+ *
* @attr ref android.R.styleable#GridView_horizontalSpacing
* @attr ref android.R.styleable#GridView_verticalSpacing
* @attr ref android.R.styleable#GridView_stretchMode
@@ -71,33 +71,33 @@
/**
* Disables stretching.
- *
- * @see #setStretchMode(int)
+ *
+ * @see #setStretchMode(int)
*/
public static final int NO_STRETCH = 0;
/**
* Stretches the spacing between columns.
- *
- * @see #setStretchMode(int)
+ *
+ * @see #setStretchMode(int)
*/
public static final int STRETCH_SPACING = 1;
/**
* Stretches columns.
- *
- * @see #setStretchMode(int)
+ *
+ * @see #setStretchMode(int)
*/
public static final int STRETCH_COLUMN_WIDTH = 2;
/**
* Stretches the spacing between columns. The spacing is uniform.
- *
- * @see #setStretchMode(int)
+ *
+ * @see #setStretchMode(int)
*/
public static final int STRETCH_SPACING_UNIFORM = 3;
/**
* Creates as many columns as can fit on screen.
- *
- * @see #setNumColumns(int)
+ *
+ * @see #setNumColumns(int)
*/
public static final int AUTO_FIT = -1;
@@ -161,7 +161,7 @@
if (index >= 0) {
setGravity(index);
}
-
+
a.recycle();
}
@@ -192,7 +192,7 @@
}
resetList();
- mRecycler.clear();
+ mRecycler.clear();
mAdapter = adapter;
mOldSelectedPosition = INVALID_POSITION;
@@ -222,7 +222,7 @@
setNextSelectedPositionInt(position);
checkSelectionChanged();
} else {
- checkFocus();
+ checkFocus();
// Nothing selected
checkSelectionChanged();
}
@@ -376,7 +376,7 @@
}
mReferenceView = child;
-
+
if (selectedView != null) {
mReferenceViewInSelectedRow = mReferenceView;
}
@@ -515,7 +515,7 @@
offsetChildrenTopAndBottom(offset);
}
}
- }
+ }
@Override
int findMotionRow(int y) {
@@ -624,7 +624,7 @@
// This is how far the bottom edge of the last view is from the bottom of the
// drawable area
- int bottomOffset = end - lastBottom;
+ int bottomOffset = end - lastBottom;
final View firstChild = getChildAt(0);
final int firstTop = firstChild.getTop();
@@ -636,7 +636,7 @@
// Don't pull the top too far down
bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
}
-
+
// Move everything down
offsetChildrenTopAndBottom(bottomOffset);
if (mFirstPosition > 0) {
@@ -679,7 +679,7 @@
// Don't pull the bottom too far up
topOffset = Math.min(topOffset, lastBottom - end);
}
-
+
// Move everything up
offsetChildrenTopAndBottom(-topOffset);
if (lastPosition < mItemCount - 1) {
@@ -965,7 +965,7 @@
final int stretchMode = mStretchMode;
final int requestedColumnWidth = mRequestedColumnWidth;
boolean didNotInitiallyFit = false;
-
+
if (mRequestedNumColumns == AUTO_FIT) {
if (requestedColumnWidth > 0) {
// Client told us to pick the number of columns
@@ -979,57 +979,57 @@
// We picked the columns
mNumColumns = mRequestedNumColumns;
}
-
+
if (mNumColumns <= 0) {
mNumColumns = 1;
}
switch (stretchMode) {
- case NO_STRETCH:
- // Nobody stretches
- mColumnWidth = requestedColumnWidth;
- mHorizontalSpacing = requestedHorizontalSpacing;
- break;
-
- default:
- int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
- ((mNumColumns - 1) * requestedHorizontalSpacing);
-
- if (spaceLeftOver < 0) {
- didNotInitiallyFit = true;
- }
-
- switch (stretchMode) {
- case STRETCH_COLUMN_WIDTH:
- // Stretch the columns
- mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
+ case NO_STRETCH:
+ // Nobody stretches
+ mColumnWidth = requestedColumnWidth;
mHorizontalSpacing = requestedHorizontalSpacing;
break;
- case STRETCH_SPACING:
- // Stretch the spacing between columns
- mColumnWidth = requestedColumnWidth;
- if (mNumColumns > 1) {
- mHorizontalSpacing = requestedHorizontalSpacing +
- spaceLeftOver / (mNumColumns - 1);
- } else {
- mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
- }
- break;
+ default:
+ int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth)
+ - ((mNumColumns - 1) * requestedHorizontalSpacing);
- case STRETCH_SPACING_UNIFORM:
- // Stretch the spacing between columns
- mColumnWidth = requestedColumnWidth;
- if (mNumColumns > 1) {
- mHorizontalSpacing = requestedHorizontalSpacing +
- spaceLeftOver / (mNumColumns + 1);
- } else {
- mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
+ if (spaceLeftOver < 0) {
+ didNotInitiallyFit = true;
}
- break;
- }
- break;
+ switch (stretchMode) {
+ case STRETCH_COLUMN_WIDTH:
+ // Stretch the columns
+ mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
+ mHorizontalSpacing = requestedHorizontalSpacing;
+ break;
+
+ case STRETCH_SPACING:
+ // Stretch the spacing between columns
+ mColumnWidth = requestedColumnWidth;
+ if (mNumColumns > 1) {
+ mHorizontalSpacing = requestedHorizontalSpacing
+ + spaceLeftOver / (mNumColumns - 1);
+ } else {
+ mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
+ }
+ break;
+
+ case STRETCH_SPACING_UNIFORM:
+ // Stretch the spacing between columns
+ mColumnWidth = requestedColumnWidth;
+ if (mNumColumns > 1) {
+ mHorizontalSpacing = requestedHorizontalSpacing
+ + spaceLeftOver / (mNumColumns + 1);
+ } else {
+ mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
+ }
+ break;
+ }
+
+ break;
}
return didNotInitiallyFit;
}
@@ -1052,7 +1052,7 @@
}
widthSize += getVerticalScrollbarWidth();
}
-
+
int childWidth = widthSize - mListPadding.left - mListPadding.right;
boolean didNotInitiallyFit = determineColumns(childWidth);
@@ -1087,7 +1087,7 @@
mRecycler.addScrapView(child, -1);
}
}
-
+
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
@@ -1095,7 +1095,7 @@
if (heightMode == MeasureSpec.AT_MOST) {
int ourSize = mListPadding.top + mListPadding.bottom;
-
+
final int numColumns = mNumColumns;
for (int i = 0; i < count; i += numColumns) {
ourSize += childHeight;
@@ -1574,9 +1574,9 @@
/**
* Sets the currently selected item
- *
+ *
* @param position Index (starting at 0) of the data item to be selected.
- *
+ *
* If in touch mode, the item will not be selected but it will still be positioned
* appropriately.
*/
@@ -1609,8 +1609,8 @@
setNextSelectedPositionInt(position);
layoutChildren();
-
- final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition :
+
+ final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition :
mNextSelectedPosition;
final int previous = mStackFromBottom ? mItemCount - 1
- previousSelectedPosition : previousSelectedPosition;
@@ -1802,7 +1802,7 @@
invokeOnItemScrollListener();
moved = true;
}
-
+
if (moved) {
awakenScrollBars();
}
@@ -1874,7 +1874,7 @@
if (moved) {
awakenScrollBars();
}
-
+
return moved;
}
@@ -2216,17 +2216,17 @@
requestLayoutIfNecessary();
}
}
-
+
/**
- * Get the number of columns in the grid.
+ * Get the number of columns in the grid.
* Returns {@link #AUTO_FIT} if the Grid has never been laid out.
*
* @attr ref android.R.styleable#GridView_numColumns
- *
+ *
* @see #setNumColumns(int)
*/
@ViewDebug.ExportedProperty
- public int getNumColumns() {
+ public int getNumColumns() {
return mNumColumns;
}
@@ -2259,13 +2259,13 @@
// we are too high, slide all views down to align with bottom
child = getChildAt(childCount - 1);
delta = child.getBottom() - (getHeight() - mListPadding.bottom);
-
+
if (mFirstPosition + childCount < mItemCount) {
// It's OK to have some space below the last item if it is
// part of the vertical spacing
delta += mVerticalSpacing;
}
-
+
if (delta > 0) {
// We only are looking to see if we are too high, not too low
delta = 0;
@@ -2277,14 +2277,14 @@
}
}
}
-
+
@Override
protected int computeVerticalScrollExtent() {
final int count = getChildCount();
if (count > 0) {
final int numColumns = mNumColumns;
final int rowCount = (count + numColumns - 1) / numColumns;
-
+
int extent = rowCount * 100;
View view = getChildAt(0);
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 369fe58..cd80651 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -476,6 +476,14 @@
* {@link #setImageBitmap(android.graphics.Bitmap)} and
* {@link android.graphics.BitmapFactory} instead.</p>
*
+ * <p class="note">On devices running SDK < 24, this method will fail to
+ * apply correct density scaling to images loaded from
+ * {@link ContentResolver#SCHEME_CONTENT content} and
+ * {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
+ * on devices with SDK >= 24 <strong>MUST</strong> specify the
+ * {@code targetSdkVersion} in their manifest as 24 or above for density
+ * scaling to be applied to images loaded from these schemes.</p>
+ *
* @param uri the Uri of an image, or {@code null} to clear the content
*/
@android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 6511de8..544e591 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,15 +32,17 @@
import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.R;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * A Layout that arranges its children in a single column or a single row. The direction of
- * the row can be set by calling {@link #setOrientation(int) setOrientation()}.
+ * A Layout that arranges its children in a single column or a single row. The direction of
+ * the row can be set by calling {@link #setOrientation(int) setOrientation()}.
* You can also specify gravity, which specifies the alignment of all the child elements by
- * calling {@link #setGravity(int) setGravity()} or specify that specific children
+ * calling {@link #setGravity(int) setGravity()} or specify that specific children
* grow to fill up any remaining space in the layout by setting the <em>weight</em> member of
* {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}.
* The default orientation is horizontal.
@@ -202,7 +202,7 @@
public LinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
-
+
public LinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -497,7 +497,7 @@
* When true, all children with a weight will be considered having
* the minimum size of the largest child. If false, all children are
* measured normally.
- *
+ *
* @return True to measure children with a weight using the minimum
* size of the largest child, false otherwise.
*
@@ -511,9 +511,9 @@
* When set to true, all children with a weight will be considered having
* the minimum size of the largest child. If false, all children are
* measured normally.
- *
+ *
* Disabled by default.
- *
+ *
* @param enabled True to measure children with a weight using the
* minimum size of the largest child, false otherwise.
*
@@ -589,7 +589,7 @@
/**
* @param i The index of the child that will be used if this layout is
* part of a larger layout that is baseline aligned.
- *
+ *
* @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
*/
@android.view.RemotableViewMethod
@@ -720,14 +720,14 @@
float totalWeight = 0;
final int count = getVirtualChildCount();
-
+
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
- final int baselineChildIndex = mBaselineAlignedChildIndex;
+ final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
@@ -886,7 +886,7 @@
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
-
+
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
@@ -991,12 +991,12 @@
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
-
+
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
-
+
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
@@ -1013,13 +1013,13 @@
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
-
+
if (lp.width == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured height
// FIXME: this may not be right for something like wrapping text?
int oldHeight = lp.height;
lp.height = child.getMeasuredHeight();
-
+
// Remeasue with new dimensions
measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
lp.height = oldHeight;
@@ -1037,7 +1037,7 @@
*
* @see #getOrientation()
* @see #setOrientation(int)
- * @see #onMeasure(int, int)
+ * @see #onMeasure(int, int)
*/
void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
@@ -1049,7 +1049,7 @@
float totalWeight = 0;
final int count = getVirtualChildCount();
-
+
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -1069,7 +1069,7 @@
final boolean baselineAligned = mBaselineAligned;
final boolean useLargestChild = mUseLargestChild;
-
+
final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
int largestChildWidth = Integer.MIN_VALUE;
@@ -1084,7 +1084,7 @@
mTotalLength += measureNullChild(i);
continue;
}
-
+
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
@@ -1263,16 +1263,16 @@
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
-
+
int widthSize = mTotalLength;
-
+
// Check against our minimum width
widthSize = Math.max(widthSize, getSuggestedMinimumWidth());
-
+
// Reconcile our calculated size with the widthMeasureSpec
int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0);
widthSize = widthSizeAndState & MEASURED_SIZE_MASK;
-
+
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
@@ -1409,12 +1409,12 @@
if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
maxHeight = alternativeMaxHeight;
}
-
+
maxHeight += mPaddingTop + mPaddingBottom;
// Check against our minimum height
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
-
+
setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
resolveSizeAndState(maxHeight, heightMeasureSpec,
(childState<<MEASURED_HEIGHT_STATE_SHIFT)));
@@ -1434,13 +1434,13 @@
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
-
+
if (lp.height == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured width
// FIXME: this may not be right for something like wrapping text?
int oldWidth = lp.width;
lp.width = child.getMeasuredWidth();
-
+
// Remeasure with new dimensions
measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
lp.width = oldWidth;
@@ -1541,14 +1541,14 @@
int childTop;
int childLeft;
-
+
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
-
+
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
-
+
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
@@ -1578,10 +1578,10 @@
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
-
+
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
-
+
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
@@ -1647,11 +1647,11 @@
int childTop;
int childLeft;
-
+
// Where bottom of child should go
final int height = bottom - top;
- int childBottom = height - mPaddingBottom;
-
+ int childBottom = height - mPaddingBottom;
+
// Space available for child
int childSpace = height - paddingTop - mPaddingBottom;
@@ -1707,12 +1707,12 @@
if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
childBaseline = child.getBaseline();
}
-
+
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
-
+
switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
childTop = paddingTop + lp.topMargin;
@@ -1764,15 +1764,15 @@
}
}
- private void setChildFrame(View child, int left, int top, int width, int height) {
+ private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
-
+
/**
* Should the layout be a column or a row.
* @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default
* value is {@link #HORIZONTAL}.
- *
+ *
* @attr ref android.R.styleable#LinearLayout_orientation
*/
public void setOrientation(@OrientationMode int orientation) {
@@ -1784,7 +1784,7 @@
/**
* Returns the current orientation.
- *
+ *
* @return either {@link #HORIZONTAL} or {@link #VERTICAL}
*/
@OrientationMode
@@ -1797,9 +1797,9 @@
* this layout has a VERTICAL orientation, this controls where all the child
* views are placed if there is extra vertical space. If this layout has a
* HORIZONTAL orientation, this controls the alignment of the children.
- *
+ *
* @param gravity See {@link android.view.Gravity}
- *
+ *
* @attr ref android.R.styleable#LinearLayout_gravity
*/
@android.view.RemotableViewMethod
@@ -1845,7 +1845,7 @@
requestLayout();
}
}
-
+
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
@@ -1909,7 +1909,7 @@
/**
* Per-child layout information associated with ViewLinearLayout.
- *
+ *
* @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
* @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
*/
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 6a10743..0bde983 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -16,9 +16,6 @@
package android.widget;
-import com.android.internal.R;
-import com.android.internal.view.menu.ShowableListMenu;
-
import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,14 +39,17 @@
import android.view.WindowManager;
import android.widget.AdapterView.OnItemSelectedListener;
+import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
+
/**
* A ListPopupWindow anchors itself to a host view and displays a
* list of choices.
- *
+ *
* <p>ListPopupWindow contains a number of tricky behaviors surrounding
* positioning, scrolling parents to fit the dropdown, interacting
* sanely with the IME if present, and others.
- *
+ *
* @see android.widget.AutoCompleteTextView
* @see android.widget.Spinner
*/
@@ -116,7 +116,7 @@
/**
* The provided prompt view should appear above list content.
- *
+ *
* @see #setPromptPosition(int)
* @see #getPromptPosition()
* @see #setPromptView(View)
@@ -125,7 +125,7 @@
/**
* The provided prompt view should appear below list content.
- *
+ *
* @see #setPromptPosition(int)
* @see #getPromptPosition()
* @see #setPromptView(View)
@@ -138,13 +138,13 @@
* If used to specify a popup height, the popup will fill available space.
*/
public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
-
+
/**
* Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
* If used to specify a popup width, the popup will use the width of its content.
*/
public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: the requirements for the
* input method should be based on the focusability of the popup. That is
@@ -152,7 +152,7 @@
* it doesn't.
*/
public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: this popup always needs to
* work with an input method, regardless of whether it is focusable. This
@@ -160,7 +160,7 @@
* the input method while it is shown.
*/
public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: this popup never needs to
* work with an input method, regardless of whether it is focusable. This
@@ -172,7 +172,7 @@
/**
* Create a new, empty popup window capable of displaying items from a ListAdapter.
* Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
- *
+ *
* @param context Context used for contained views.
*/
public ListPopupWindow(@NonNull Context context) {
@@ -182,7 +182,7 @@
/**
* Create a new, empty popup window capable of displaying items from a ListAdapter.
* Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
- *
+ *
* @param context Context used for contained views.
* @param attrs Attributes from inflating parent views used to style the popup.
*/
@@ -193,7 +193,7 @@
/**
* Create a new, empty popup window capable of displaying items from a ListAdapter.
* Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
- *
+ *
* @param context Context used for contained views.
* @param attrs Attributes from inflating parent views used to style the popup.
* @param defStyleAttr Default style attribute to use for popup content.
@@ -206,7 +206,7 @@
/**
* Create a new, empty popup window capable of displaying items from a ListAdapter.
* Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
- *
+ *
* @param context Context used for contained views.
* @param attrs Attributes from inflating parent views used to style the popup.
* @param defStyleAttr Style attribute to read for default styling of popup content.
@@ -248,7 +248,7 @@
if (mAdapter != null) {
adapter.registerDataSetObserver(mObserver);
}
-
+
if (mDropDownList != null) {
mDropDownList.setAdapter(mAdapter);
}
@@ -257,9 +257,9 @@
/**
* Set where the optional prompt view should appear. The default is
* {@link #POSITION_PROMPT_ABOVE}.
- *
+ *
* @param position A position constant declaring where the prompt should be displayed.
- *
+ *
* @see #POSITION_PROMPT_ABOVE
* @see #POSITION_PROMPT_BELOW
*/
@@ -269,7 +269,7 @@
/**
* @return Where the optional prompt view should appear.
- *
+ *
* @see #POSITION_PROMPT_ABOVE
* @see #POSITION_PROMPT_BELOW
*/
@@ -279,11 +279,11 @@
/**
* Set whether this window should be modal when shown.
- *
+ *
* <p>If a popup window is modal, it will receive all touch and key input.
* If the user touches outside the popup window's content area the popup window
* will be dismissed.
- *
+ *
* @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
*/
public void setModal(boolean modal) {
@@ -293,7 +293,7 @@
/**
* Returns whether the popup window will be modal when shown.
- *
+ *
* @return {@code true} if the popup window will be modal, {@code false} otherwise.
*/
public boolean isModal() {
@@ -304,7 +304,7 @@
* Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
* false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
* ignore outside touch even when the drop down is not set to always visible.
- *
+ *
* @hide Used only by AutoCompleteTextView to handle some internal special cases.
*/
public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
@@ -361,7 +361,7 @@
/**
* Sets a drawable to use as the list item selector.
- *
+ *
* @param selector List selector drawable to use in the popup.
*/
public void setListSelector(Drawable selector) {
@@ -377,7 +377,7 @@
/**
* Sets a drawable to be the background for the popup window.
- *
+ *
* @param d A drawable to set as the background.
*/
public void setBackgroundDrawable(@Nullable Drawable d) {
@@ -386,7 +386,7 @@
/**
* Set an animation style to use when the popup window is shown or dismissed.
- *
+ *
* @param animationStyle Animation style to use.
*/
public void setAnimationStyle(@StyleRes int animationStyle) {
@@ -396,7 +396,7 @@
/**
* Returns the animation style that will be used when the popup window is
* shown or dismissed.
- *
+ *
* @return Animation style that will be used.
*/
public @StyleRes int getAnimationStyle() {
@@ -405,7 +405,7 @@
/**
* Returns the view that will be used to anchor this popup.
- *
+ *
* @return The popup's anchor view
*/
public @Nullable View getAnchorView() {
@@ -415,7 +415,7 @@
/**
* Sets the popup's anchor view. This popup will always be positioned relative to
* the anchor view when shown.
- *
+ *
* @param anchor The view to use as an anchor.
*/
public void setAnchorView(@Nullable View anchor) {
@@ -431,7 +431,7 @@
/**
* Set the horizontal offset of this popup from its anchor view in pixels.
- *
+ *
* @param offset The horizontal offset of the popup from its anchor.
*/
public void setHorizontalOffset(int offset) {
@@ -450,7 +450,7 @@
/**
* Set the vertical offset of this popup from its anchor view in pixels.
- *
+ *
* @param offset The vertical offset of the popup from its anchor.
*/
public void setVerticalOffset(int offset) {
@@ -489,7 +489,7 @@
/**
* Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
* or {@link #WRAP_CONTENT}.
- *
+ *
* @param width Width of the popup window.
*/
public void setWidth(int width) {
@@ -521,7 +521,7 @@
/**
* Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
- *
+ *
* @param height Height of the popup window.
*/
public void setHeight(int height) {
@@ -543,9 +543,9 @@
/**
* Sets a listener to receive events when a list item is clicked.
- *
+ *
* @param clickListener Listener to register
- *
+ *
* @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
*/
public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
@@ -554,9 +554,9 @@
/**
* Sets a listener to receive events when a list item is selected.
- *
+ *
* @param selectedListener Listener to register.
- *
+ *
* @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
*/
public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
@@ -566,7 +566,7 @@
/**
* Set a view to act as a user prompt for this popup window. Where the prompt view will appear
* is controlled by {@link #setPromptPosition(int)}.
- *
+ *
* @param prompt View to use as an informational prompt.
*/
public void setPromptView(@Nullable View prompt) {
@@ -662,7 +662,7 @@
mPopup.setWidth(widthSpec);
mPopup.setHeight(heightSpec);
mPopup.setClipToScreenEnabled(true);
-
+
// use outside touchable to dismiss drop down when touching outside of it, so
// only set this if the dropdown is not always visible
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
@@ -671,7 +671,7 @@
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
-
+
if (!mModal || mDropDownList.isInTouchMode()) {
clearListSelection();
}
@@ -716,11 +716,11 @@
* Control how the popup operates with an input method: one of
* {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
* or {@link #INPUT_METHOD_NOT_NEEDED}.
- *
+ *
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to the {@link #show()}
* method.</p>
- *
+ *
* @see #getInputMethodMode()
* @see #show()
*/
@@ -730,7 +730,7 @@
/**
* Return the current value in {@link #setInputMethodMode(int)}.
- *
+ *
* @see #setInputMethodMode(int)
*/
public int getInputMethodMode() {
@@ -740,7 +740,7 @@
/**
* Set the selected position of the list.
* Only valid when {@link #isShowing()} == {@code true}.
- *
+ *
* @param position List position to set as selected.
*/
public void setSelection(int position) {
@@ -786,7 +786,7 @@
/**
* Perform an item click operation on the specified list adapter position.
- *
+ *
* @param position Adapter position for performing the click
* @return true if the click action could be performed, false if not.
* (e.g. if the popup was not showing, this method would return false.)
@@ -817,7 +817,7 @@
/**
* @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
* if {@link #isShowing()} == {@code false}.
- *
+ *
* @see ListView#getSelectedItemPosition()
*/
public int getSelectedItemPosition() {
@@ -830,7 +830,7 @@
/**
* @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
* if {@link #isShowing()} == {@code false}.
- *
+ *
* @see ListView#getSelectedItemId()
*/
public long getSelectedItemId() {
@@ -843,7 +843,7 @@
/**
* @return The View for the currently selected item or null if
* {@link #isShowing()} == {@code false}.
- *
+ *
* @see ListView#getSelectedView()
*/
public @Nullable View getSelectedView() {
@@ -904,7 +904,7 @@
final boolean below = !mPopup.isAboveAnchor();
final ListAdapter adapter = mAdapter;
-
+
boolean allEnabled;
int firstItem = Integer.MAX_VALUE;
int lastItem = Integer.MIN_VALUE;
@@ -914,9 +914,9 @@
firstItem = allEnabled ? 0 :
mDropDownList.lookForSelectablePosition(0, true);
lastItem = allEnabled ? adapter.getCount() - 1 :
- mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
+ mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
}
-
+
if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
(!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
// When the selection is at the top, we block the key
@@ -1132,18 +1132,18 @@
LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
);
-
+
switch (mPromptPosition) {
case POSITION_PROMPT_BELOW:
hintContainer.addView(dropDownView, hintParams);
hintContainer.addView(hintView);
break;
-
+
case POSITION_PROMPT_ABOVE:
hintContainer.addView(hintView);
hintContainer.addView(dropDownView, hintParams);
break;
-
+
default:
Log.e(TAG, "Invalid hint position " + mPromptPosition);
break;
@@ -1249,7 +1249,7 @@
show();
}
}
-
+
@Override
public void onInvalidated() {
dismiss();
@@ -1278,7 +1278,7 @@
final int action = event.getAction();
final int x = (int) event.getX();
final int y = (int) event.getY();
-
+
if (action == MotionEvent.ACTION_DOWN &&
mPopup != null && mPopup.isShowing() &&
(x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index cfe1c09..e52c13b 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -16,11 +16,6 @@
package android.widget;
-import com.google.android.collect.Lists;
-
-import com.android.internal.R;
-import com.android.internal.util.Predicate;
-
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -53,6 +48,11 @@
import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.R;
+import com.android.internal.util.Predicate;
+
+import com.google.android.collect.Lists;
+
import java.util.ArrayList;
import java.util.List;
@@ -464,7 +464,7 @@
* data backing this list and for producing a view to represent an
* item in that data set.
*
- * @see #getAdapter()
+ * @see #getAdapter()
*/
@Override
public void setAdapter(ListAdapter adapter) {
@@ -1532,7 +1532,7 @@
adjustViewsUpOrDown();
}
} else if (lastPosition == mItemCount - 1) {
- adjustViewsUpOrDown();
+ adjustViewsUpOrDown();
}
}
}
@@ -1857,7 +1857,7 @@
&& focusLayoutRestoreView.getWindowToken() != null) {
focusLayoutRestoreView.dispatchFinishTemporaryDetach();
}
-
+
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
if (mPositionScrollAfterLayout != null) {
@@ -2109,7 +2109,7 @@
/**
* Makes the item at the supplied position selected.
- *
+ *
* @param position the position of the item to select
*/
@Override
@@ -2960,7 +2960,7 @@
if (startPos < firstPosition) {
startPos = firstPosition;
}
-
+
final int lastVisiblePos = getLastVisiblePosition();
final ListAdapter adapter = getAdapter();
for (int pos = startPos; pos <= lastVisiblePos; pos++) {
@@ -3129,7 +3129,7 @@
/**
* Determine the distance to the nearest edge of a view in a particular
* direction.
- *
+ *
* @param descendant A descendant of this list.
* @return The distance, or 0 if the nearest edge is already on screen.
*/
@@ -3386,7 +3386,7 @@
final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
if (!mStackFromBottom) {
int bottom = 0;
-
+
// Draw top divider or header for overscroll
final int scrollY = mScrollY;
if (count > 0 && scrollY < 0) {
@@ -3483,7 +3483,7 @@
}
}
}
-
+
if (count > 0 && scrollY > 0) {
if (drawOverscrollFooter) {
final int absListBottom = mBottom;
@@ -3567,7 +3567,7 @@
public int getDividerHeight() {
return mDividerHeight;
}
-
+
/**
* Sets the height of the divider that will be drawn between each item in the list. Calling
* this will override the intrinsic height as set by {@link #setDivider(Drawable)}
@@ -3625,7 +3625,7 @@
public boolean areFooterDividersEnabled() {
return mFooterDividersEnabled;
}
-
+
/**
* Sets the drawable that will be drawn above all other list content.
* This area can become visible when the user overscrolls the list.
@@ -3878,10 +3878,10 @@
/**
* Returns the set of checked items ids. The result is only valid if the
* choice mode has not been set to {@link #CHOICE_MODE_NONE}.
- *
+ *
* @return A new array which contains the id of each checked item in the
* list.
- *
+ *
* @deprecated Use {@link #getCheckedItemIds()} instead.
*/
@Deprecated
diff --git a/core/java/android/widget/MenuItemHoverListener.java b/core/java/android/widget/MenuItemHoverListener.java
index 13f0e6a..835e4cd 100644
--- a/core/java/android/widget/MenuItemHoverListener.java
+++ b/core/java/android/widget/MenuItemHoverListener.java
@@ -1,10 +1,10 @@
package android.widget;
-import com.android.internal.view.menu.MenuBuilder;
-
import android.annotation.NonNull;
import android.view.MenuItem;
+import com.android.internal.view.menu.MenuBuilder;
+
/**
* An interface notified when a menu item is hovered. Useful for cases when hover should trigger
* some behavior at a higher level, like managing the opening and closing of submenus.
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 72b2e31..662e640 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.TestApi;
@@ -32,12 +30,10 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
-import android.text.TextWatcher;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -57,6 +53,10 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.R;
+
+import libcore.icu.LocaleData;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -64,8 +64,6 @@
import java.util.List;
import java.util.Locale;
-import libcore.icu.LocaleData;
-
/**
* A widget that enables the user to select a number from a predefined range.
* There are two flavors of this widget and which one is presented to the user
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 027f874..eb27533 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -16,11 +16,6 @@
package android.widget;
-import com.android.internal.R;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuPopupHelper;
-import com.android.internal.view.menu.ShowableListMenu;
-
import android.annotation.MenuRes;
import android.content.Context;
import android.view.Gravity;
@@ -30,6 +25,11 @@
import android.view.View;
import android.view.View.OnTouchListener;
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.ShowableListMenu;
+
/**
* A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
* {@link View}. The popup will appear below the anchor view if there is room,
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 5e73a63..fc1520b 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,7 +16,11 @@
package android.widget;
-import com.android.internal.R;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams
+ .PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -50,12 +54,9 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import java.lang.ref.WeakReference;
+import com.android.internal.R;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import java.lang.ref.WeakReference;
/**
* <p>
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 3c967ac..266ff75 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
@@ -59,6 +58,7 @@
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
+
import com.android.internal.R;
import java.util.ArrayList;
@@ -185,6 +185,7 @@
* @attr ref android.R.styleable#ProgressBar_indeterminateDuration
* @attr ref android.R.styleable#ProgressBar_indeterminateOnly
* @attr ref android.R.styleable#ProgressBar_interpolator
+ * @attr ref android.R.styleable#ProgressBar_min
* @attr ref android.R.styleable#ProgressBar_max
* @attr ref android.R.styleable#ProgressBar_maxHeight
* @attr ref android.R.styleable#ProgressBar_maxWidth
@@ -215,7 +216,10 @@
private int mProgress;
private int mSecondaryProgress;
+ private int mMin;
+ private boolean mMinInitialized;
private int mMax;
+ private boolean mMaxInitialized;
private int mBehavior;
private int mDuration;
@@ -230,7 +234,7 @@
private Drawable mCurrentDrawable;
private ProgressTintInfo mProgressTintInfo;
- Bitmap mSampleTile;
+ int mSampleWidth = 0;
private boolean mNoInvalidate;
private Interpolator mInterpolator;
private RefreshProgressRunnable mRefreshProgressRunnable;
@@ -308,6 +312,7 @@
setInterpolator(context, resID);
}
+ setMin(a.getInt(R.styleable.ProgressBar_min, mMin));
setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
@@ -505,15 +510,14 @@
}
if (drawable instanceof BitmapDrawable) {
- final BitmapDrawable bitmap = (BitmapDrawable) drawable;
- final Bitmap tileBitmap = bitmap.getBitmap();
- if (mSampleTile == null) {
- mSampleTile = tileBitmap;
- }
-
- final BitmapDrawable clone = (BitmapDrawable) bitmap.getConstantState().newDrawable();
+ final Drawable.ConstantState cs = drawable.getConstantState();
+ final BitmapDrawable clone = (BitmapDrawable) cs.newDrawable(getResources());
clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+ if (mSampleWidth <= 0) {
+ mSampleWidth = clone.getIntrinsicWidth();
+ }
+
if (clip) {
return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL);
} else {
@@ -565,6 +569,7 @@
* </ul>
*/
private void initProgressBar() {
+ mMin = 0;
mMax = 100;
mProgress = 0;
mSecondaryProgress = 0;
@@ -1310,7 +1315,8 @@
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
boolean callBackToApp, boolean animate) {
- final float scale = mMax > 0 ? progress / (float) mMax : 0;
+ int range = mMax - mMin;
+ final float scale = range > 0 ? (progress - mMin) / (float) range : 0;
final boolean isPrimary = id == R.id.progress;
if (isPrimary && animate) {
@@ -1436,7 +1442,7 @@
return false;
}
- progress = MathUtils.constrain(progress, 0, mMax);
+ progress = MathUtils.constrain(progress, mMin, mMax);
if (progress == mProgress) {
// No change from current.
@@ -1466,8 +1472,8 @@
return;
}
- if (secondaryProgress < 0) {
- secondaryProgress = 0;
+ if (secondaryProgress < mMin) {
+ secondaryProgress = mMin;
}
if (secondaryProgress > mMax) {
@@ -1515,6 +1521,20 @@
}
/**
+ * <p>Return the lower limit of this progress bar's range.</p>
+ *
+ * @return a positive integer
+ *
+ * @see #setMin(int)
+ * @see #getProgress()
+ * @see #getSecondaryProgress()
+ */
+ @ViewDebug.ExportedProperty(category = "progress")
+ public synchronized int getMin() {
+ return mMin;
+ }
+
+ /**
* <p>Return the upper limit of this progress bar's range.</p>
*
* @return a positive integer
@@ -1529,7 +1549,37 @@
}
/**
- * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
+ * <p>Set the lower range of the progress bar to <tt>min</tt>.</p>
+ *
+ * @param min the lower range of this progress bar
+ *
+ * @see #getMin()
+ * @see #setProgress(int)
+ * @see #setSecondaryProgress(int)
+ */
+ @android.view.RemotableViewMethod
+ public synchronized void setMin(int min) {
+ if (mMaxInitialized) {
+ if (min > mMax) {
+ min = mMax;
+ }
+ }
+ mMinInitialized = true;
+ if (mMaxInitialized && min != mMin) {
+ mMin = min;
+ postInvalidate();
+
+ if (mProgress < min) {
+ mProgress = min;
+ }
+ refreshProgress(R.id.progress, mProgress, false, false);
+ } else {
+ mMin = min;
+ }
+ }
+
+ /**
+ * <p>Set the upper range of the progress bar <tt>max</tt>.</p>
*
* @param max the upper range of this progress bar
*
@@ -1539,10 +1589,13 @@
*/
@android.view.RemotableViewMethod
public synchronized void setMax(int max) {
- if (max < 0) {
- max = 0;
+ if (mMinInitialized) {
+ if (max < mMin) {
+ max = mMin;
+ }
}
- if (max != mMax) {
+ mMaxInitialized = true;
+ if (mMinInitialized && max != mMax) {
mMax = max;
postInvalidate();
@@ -1550,6 +1603,8 @@
mProgress = max;
}
refreshProgress(R.id.progress, mProgress, false, false);
+ } else {
+ mMax = max;
}
}
@@ -1959,7 +2014,7 @@
@Override
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
super.onInitializeAccessibilityEventInternal(event);
- event.setItemCount(mMax);
+ event.setItemCount(mMax - mMin);
event.setCurrentItemIndex(mProgress);
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 8c15cde..8f6b0d5 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.Context;
@@ -38,6 +36,8 @@
import android.view.View;
import android.view.View.OnClickListener;
+import com.android.internal.R;
+
/**
* Widget used to show an image with the standard QuickContact badge
* and on-click behavior.
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 5a0e1f9..757a4ca 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -16,10 +16,6 @@
package android.widget;
-import android.view.PointerIcon;
-import com.android.internal.R;
-import com.android.internal.widget.ExploreByTouchHelper;
-
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.content.Context;
@@ -43,11 +39,15 @@
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Calendar;
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 065feb8..54b57631 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.IdRes;
import android.content.Context;
import android.content.res.TypedArray;
@@ -25,6 +23,8 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.R;
+
/**
* <p>This class is used to create a multiple-exclusion scope for a set of radio
@@ -39,14 +39,14 @@
* in the XML layout file.</p>
*
* <p><strong>XML Attributes</strong></p>
- * <p>See {@link android.R.styleable#RadioGroup RadioGroup Attributes},
+ * <p>See {@link android.R.styleable#RadioGroup RadioGroup Attributes},
* {@link android.R.styleable#LinearLayout LinearLayout Attributes},
* {@link android.R.styleable#ViewGroup ViewGroup Attributes},
* {@link android.R.styleable#View View Attributes}</p>
* <p>Also see
* {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}
* for layout attributes.</p>
- *
+ *
* @see RadioButton
*
*/
@@ -310,7 +310,7 @@
} else {
width = WRAP_CONTENT;
}
-
+
if (a.hasValue(heightAttr)) {
height = a.getLayoutDimension(heightAttr, "layout_height");
} else {
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 3ad05b5..3b7fe86 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -22,6 +22,7 @@
import android.graphics.drawable.shapes.Shape;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityNodeInfo;
+
import com.android.internal.R;
/**
@@ -281,10 +282,8 @@
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSampleTile != null) {
- // TODO: Once ProgressBar's TODOs are gone, this can be done more
- // cleanly than mSampleTile
- final int width = mSampleTile.getWidth() * mNumStars;
+ if (mSampleWidth > 0) {
+ final int width = mSampleWidth * mNumStars;
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0),
getMeasuredHeight());
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index a189d3c..b424101 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -16,20 +16,14 @@
package android.widget;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
import android.annotation.NonNull;
-import android.util.ArrayMap;
-import com.android.internal.R;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Build;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
@@ -41,7 +35,13 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import com.android.internal.R;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
/**
* A Layout where the positions of the children can be described in relation to each other or to the
@@ -209,7 +209,7 @@
private int mIgnoreGravity;
private SortedSet<View> mTopToBottomLeftToRightSet = null;
-
+
private boolean mDirtyHierarchy;
private View[] mSortedHorizontalChildren;
private View[] mSortedVerticalChildren;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ea4bd13..316dab5 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -59,11 +59,12 @@
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView.OnItemClickListener;
-import libcore.util.Objects;
import com.android.internal.R;
import com.android.internal.util.Preconditions;
+import libcore.util.Objects;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index d41bd46..11e0a3f 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -47,6 +47,11 @@
import java.util.LinkedList;
import java.util.concurrent.Executor;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+
/**
* An adapter to a RemoteViewsService which fetches and caches RemoteViews
* to be later inflated as child views.
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 07bd918..2827f63 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -16,14 +16,14 @@
package android.widget;
-import java.util.HashMap;
-
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.android.internal.widget.IRemoteViewsFactory;
+import java.util.HashMap;
+
/**
* The service to be connected to for a remote adapter to request RemoteViews. Users should
* extend the RemoteViewsService to provide the appropriate RemoteViewsFactory's used to
@@ -46,7 +46,7 @@
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
* for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
- *
+ *
* @see android.widget.Adapter
* @see android.appwidget.AppWidgetManager
*/
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index 100f919..9732bb1 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -20,9 +20,9 @@
import android.content.res.Resources;
import android.database.Cursor;
import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
/**
@@ -148,7 +148,7 @@
public void setViewResource(int layout) {
mLayout = layout;
}
-
+
/**
* <p>Sets the layout resource of the drop down views.</p>
*
diff --git a/core/java/android/widget/ResourceCursorTreeAdapter.java b/core/java/android/widget/ResourceCursorTreeAdapter.java
index ddce515..0894dba 100644
--- a/core/java/android/widget/ResourceCursorTreeAdapter.java
+++ b/core/java/android/widget/ResourceCursorTreeAdapter.java
@@ -18,9 +18,9 @@
import android.content.Context;
import android.database.Cursor;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
/**
* A fairly simple ExpandableListAdapter that creates views defined in an XML
@@ -32,10 +32,10 @@
private int mChildLayout;
private int mLastChildLayout;
private LayoutInflater mInflater;
-
+
/**
* Constructor.
- *
+ *
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
* @param cursor The database cursor
@@ -51,18 +51,18 @@
public ResourceCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
int expandedGroupLayout, int childLayout, int lastChildLayout) {
super(cursor, context);
-
+
mCollapsedGroupLayout = collapsedGroupLayout;
mExpandedGroupLayout = expandedGroupLayout;
mChildLayout = childLayout;
mLastChildLayout = lastChildLayout;
-
+
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor.
- *
+ *
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
* @param cursor The database cursor
@@ -80,7 +80,7 @@
/**
* Constructor.
- *
+ *
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
* @param cursor The database cursor
@@ -93,7 +93,7 @@
int childLayout) {
this(context, cursor, groupLayout, groupLayout, childLayout, childLayout);
}
-
+
@Override
public View newChildView(Context context, Cursor cursor, boolean isLastChild,
ViewGroup parent) {
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 11eab2a..2ae38c9 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.widget.ScrollBarUtils;
-
import android.annotation.NonNull;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
@@ -25,6 +23,8 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import com.android.internal.widget.ScrollBarUtils;
+
/**
* This is only used by View for displaying its scroll bars. It should probably
* be moved in to the view package since it is used in that lower-level layer.
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index e696ff7..d8f3379 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -17,18 +17,16 @@
package android.widget;
import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.R;
-
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.Log;
@@ -47,6 +45,8 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
+import com.android.internal.R;
+
import java.util.List;
/**
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
index 5d01d8d..f9aced0 100644
--- a/core/java/android/widget/SeekBar.java
+++ b/core/java/android/widget/SeekBar.java
@@ -46,8 +46,10 @@
* to distinguish user-initiated changes from those that occurred programmatically.
*
* @param seekBar The SeekBar whose progress has changed
- * @param progress The current progress level. This will be in the range 0..max where max
- * was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.)
+ * @param progress The current progress level. This will be in the range min..max where min
+ * and max were set by {@link ProgressBar#setMin(int)} and
+ * {@link ProgressBar#setMax(int)}, respectively. (The default values for
+ * min is 0 and max is 100.)
* @param fromUser True if the progress change was initiated by the user.
*/
void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser);
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index 3bf9485..9190117 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -20,11 +20,11 @@
import android.annotation.LayoutRes;
import android.content.Context;
import android.content.res.Resources;
+import android.net.Uri;
import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
-import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
@@ -40,7 +40,7 @@
* Binding data to views occurs in two phases. First, if a
* {@link android.widget.SimpleAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, Object, String)}
- * is invoked. If the returned value is true, binding has occurred.
+ * is invoked. If the returned value is true, binding has occurred.
* If the returned value is false, the following views are then tried in order:
* <ul>
* <li> A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean.
@@ -223,7 +223,7 @@
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
- setViewImage((ImageView) v, (Integer) data);
+ setViewImage((ImageView) v, (Integer) data);
} else {
setViewImage((ImageView) v, text);
}
@@ -291,7 +291,7 @@
* @param v ImageView to receive an image
* @param value the value retrieved from the data set
*
- * @see #setViewImage(ImageView, int)
+ * @see #setViewImage(ImageView, int)
*/
public void setViewImage(ImageView v, String value) {
try {
@@ -381,18 +381,18 @@
for (int i = 0; i < count; i++) {
Map<String, ?> h = unfilteredValues.get(i);
if (h != null) {
-
+
int len = mTo.length;
for (int j=0; j<len; j++) {
String str = (String)h.get(mFrom[j]);
-
+
String[] words = str.split(" ");
int wordCount = words.length;
-
+
for (int k = 0; k < wordCount; k++) {
String word = words[k];
-
+
if (word.toLowerCase().startsWith(prefixString)) {
newValues.add(h);
break;
diff --git a/core/java/android/widget/SimpleExpandableListAdapter.java b/core/java/android/widget/SimpleExpandableListAdapter.java
index 015c169..597502b 100644
--- a/core/java/android/widget/SimpleExpandableListAdapter.java
+++ b/core/java/android/widget/SimpleExpandableListAdapter.java
@@ -17,9 +17,9 @@
package android.widget;
import android.content.Context;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
import java.util.List;
import java.util.Map;
@@ -42,18 +42,18 @@
private int mCollapsedGroupLayout;
private String[] mGroupFrom;
private int[] mGroupTo;
-
+
private List<? extends List<? extends Map<String, ?>>> mChildData;
private int mChildLayout;
private int mLastChildLayout;
private String[] mChildFrom;
private int[] mChildTo;
-
+
private LayoutInflater mInflater;
-
+
/**
* Constructor
- *
+ *
* @param context The context where the {@link ExpandableListView}
* associated with this {@link SimpleExpandableListAdapter} is
* running
@@ -98,7 +98,7 @@
/**
* Constructor
- *
+ *
* @param context The context where the {@link ExpandableListView}
* associated with this {@link SimpleExpandableListAdapter} is
* running
@@ -147,7 +147,7 @@
/**
* Constructor
- *
+ *
* @param context The context where the {@link ExpandableListView}
* associated with this {@link SimpleExpandableListAdapter} is
* running
@@ -200,16 +200,16 @@
mCollapsedGroupLayout = collapsedGroupLayout;
mGroupFrom = groupFrom;
mGroupTo = groupTo;
-
+
mChildData = childData;
mChildLayout = childLayout;
mLastChildLayout = lastChildLayout;
mChildFrom = childFrom;
mChildTo = childTo;
-
+
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
-
+
public Object getChild(int groupPosition, int childPosition) {
return mChildData.get(groupPosition).get(childPosition);
}
@@ -239,7 +239,7 @@
public View newChildView(boolean isLastChild, ViewGroup parent) {
return mInflater.inflate((isLastChild) ? mLastChildLayout : mChildLayout, parent, false);
}
-
+
private void bindView(View view, Map<String, ?> data, String[] from, int[] to) {
int len = to.length;
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 8c43782..855d1d2 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -16,10 +16,6 @@
package android.widget;
-import android.view.PointerIcon;
-import com.android.internal.R;
-import com.android.internal.widget.ExploreByTouchHelper;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -43,17 +39,21 @@
import android.util.StateSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import java.text.NumberFormat;
-import java.util.Locale;
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
import libcore.icu.LocaleData;
+import java.text.NumberFormat;
+import java.util.Locale;
+
/**
* A calendar-like view displaying a specified month and the appropriate selectable day numbers
* within the specified month.
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index dc5e5a2..28cc693 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -16,13 +16,9 @@
package android.widget;
-import android.annotation.TestApi;
-import android.view.PointerIcon;
-import com.android.internal.R;
-import com.android.internal.view.menu.ShowableListMenu;
-
import android.annotation.DrawableRes;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.Widget;
import android.app.AlertDialog;
import android.content.Context;
@@ -42,6 +38,7 @@
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -49,6 +46,9 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.PopupWindow.OnDismissListener;
+import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
+
/**
* A view that displays one child at a time and lets the user pick among them.
* The items in the Spinner come from the {@link Adapter} associated with
@@ -123,7 +123,7 @@
* access the current theme, resources, etc.
* @param mode Constant describing how the user will select choices from
* the spinner.
- *
+ *
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
@@ -563,7 +563,7 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
-
+
if (mPopup != null && mPopup.isShowing()) {
mPopup.dismiss();
}
@@ -772,7 +772,7 @@
@Override
public boolean performClick() {
boolean handled = super.performClick();
-
+
if (!handled) {
handled = true;
@@ -1011,7 +1011,7 @@
/**
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
- * Otherwise, return true.
+ * Otherwise, return true.
*/
public boolean areAllItemsEnabled() {
final ListAdapter adapter = mListAdapter;
@@ -1042,19 +1042,19 @@
public int getViewTypeCount() {
return 1;
}
-
+
public boolean isEmpty() {
return getCount() == 0;
}
}
-
+
/**
* Implements some sort of popup selection interface for selecting a spinner option.
* Allows for different spinner modes.
*/
private interface SpinnerPopup {
public void setAdapter(ListAdapter adapter);
-
+
/**
* Show the popup
*/
@@ -1064,12 +1064,12 @@
* Dismiss the popup
*/
public void dismiss();
-
+
/**
* @return true if the popup is showing, false otherwise.
*/
public boolean isShowing();
-
+
/**
* Set hint text to be displayed to the user. This should provide
* a description of the choice being made.
@@ -1129,7 +1129,7 @@
listView.setTextAlignment(textAlignment);
mPopup.show();
}
-
+
public void onClick(DialogInterface dialog, int which) {
setSelection(which);
if (mOnItemClickListener != null) {
@@ -1168,7 +1168,7 @@
return 0;
}
}
-
+
private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
private CharSequence mHintText;
private ListAdapter mAdapter;
@@ -1190,7 +1190,7 @@
}
});
}
-
+
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
@@ -1200,7 +1200,7 @@
public CharSequence getHintText() {
return mHintText;
}
-
+
public void setPromptText(CharSequence hintText) {
// Hint text is ignored for dropdowns, but maintain it here.
mHintText = hintText;
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 2bd3143..0e99c02 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -15,8 +15,6 @@
package android.widget;
-import java.lang.ref.WeakReference;
-
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -45,6 +43,8 @@
import android.view.animation.LinearInterpolator;
import android.widget.RemoteViews.RemoteView;
+import java.lang.ref.WeakReference;
+
@RemoteView
/**
* A view that displays its children in a stack and allows users to discretely swipe
@@ -670,12 +670,12 @@
activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
}
- boolean endOfStack = mLoopViews && adapterCount == 1 &&
- ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP) ||
- (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN));
- boolean beginningOfStack = mLoopViews && adapterCount == 1 &&
- ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP) ||
- (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN));
+ boolean endOfStack = mLoopViews && adapterCount == 1
+ && ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP)
+ || (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN));
+ boolean beginningOfStack = mLoopViews && adapterCount == 1
+ && ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP)
+ || (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN));
int stackMode;
if (mLoopViews && !beginningOfStack && !endOfStack) {
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index aad0625..f833d1b 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -21,8 +21,8 @@
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.ContentResolver.OpenResourceIdResult;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -39,8 +39,8 @@
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
import com.android.internal.R;
@@ -111,7 +111,7 @@
mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
mOutsideDrawablesCache = outsideDrawablesCache;
-
+
// mStartSpinnerRunnable = new Runnable() {
// public void run() {
// // mSearchView.setWorking(true); // TODO:
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index d51c5be..fcc1667 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -30,8 +30,8 @@
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Typeface;
import android.graphics.Region.Op;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
@@ -46,8 +46,8 @@
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
-import android.view.ViewStructure;
import android.view.ViewConfiguration;
+import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 583f037..32418cd 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.LocalActivityManager;
@@ -35,6 +33,9 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
+
+import com.android.internal.R;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 1f0cb7c..05f7c0a 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -16,10 +16,6 @@
package android.widget;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
-import com.android.internal.R;
-
import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
@@ -29,11 +25,15 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+
/**
*
* Displays a list of tab labels representing each page in the parent's tab
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index eed3c2d..8bb4d16 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -16,14 +16,15 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewGroup;
+
+import com.android.internal.R;
+
import java.util.regex.Pattern;
/**
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 278ceb2..a6a9db4 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -16,6 +16,9 @@
package android.widget;
+import static android.view.ViewDebug.ExportedProperty;
+import static android.widget.RemoteViews.RemoteView;
+
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
@@ -37,13 +40,10 @@
import com.android.internal.R;
-import java.util.Calendar;
-import java.util.TimeZone;
-
import libcore.icu.LocaleData;
-import static android.view.ViewDebug.ExportedProperty;
-import static android.widget.RemoteViews.*;
+import java.util.Calendar;
+import java.util.TimeZone;
/**
* <p><code>TextClock</code> can display the current date and/or time as
@@ -132,7 +132,7 @@
private CharSequence mDescFormat;
- private boolean mAttached;
+ private boolean mRegistered;
private Calendar mTime;
private String mTimeZone;
@@ -252,7 +252,7 @@
}
createTime(mTimeZone);
- // Wait until onAttachedToWindow() to handle the ticker
+ // Wait until registering for events to handle the ticker
chooseFormat(false);
}
@@ -503,7 +503,7 @@
boolean hadSeconds = mHasSeconds;
mHasSeconds = DateFormat.hasSeconds(mFormat);
- if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
+ if (handleTicker && mRegistered && hadSeconds != mHasSeconds) {
if (hadSeconds) getHandler().removeCallbacks(mTicker);
else mTicker.run();
}
@@ -517,11 +517,9 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (!mAttached) {
- mAttached = true;
+ public void onVisibilityAggregated(boolean isVisible) {
+ if (!mRegistered && isVisible) {
+ mRegistered = true;
registerReceiver();
registerObserver();
@@ -533,20 +531,13 @@
} else {
onTimeChanged();
}
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (mAttached) {
+ } else if (mRegistered && !isVisible) {
unregisterReceiver();
unregisterObserver();
getHandler().removeCallbacks(mTicker);
- mAttached = false;
+ mRegistered = false;
}
}
@@ -569,7 +560,7 @@
}
private void registerObserver() {
- if (isAttachedToWindow()) {
+ if (mRegistered) {
if (mFormatChangeObserver == null) {
mFormatChangeObserver = new FormatChangeObserver(getHandler());
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5264d5c..2f9c97b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -41,6 +41,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.BaseCanvas;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
@@ -3684,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();
@@ -3703,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
*/
@@ -3715,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();
@@ -3731,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
*/
@@ -3743,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();
@@ -3759,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
*/
@@ -3771,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();
@@ -3788,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
*/
@@ -3800,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
*/
@@ -3817,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
*/
@@ -3836,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();
@@ -3850,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)
@@ -3863,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();
@@ -3877,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)
@@ -3890,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();
@@ -3904,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)
@@ -3917,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();
@@ -3931,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)
@@ -3944,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
*/
@@ -3963,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
*/
@@ -10222,12 +10333,14 @@
System.arraycopy(mChars, start + mStart, buf, off, end - start);
}
- public void drawText(Canvas c, int start, int end,
+ @Override
+ public void drawText(BaseCanvas c, int start, int end,
float x, float y, Paint p) {
c.drawText(mChars, start + mStart, end - start, x, y, p);
}
- public void drawTextRun(Canvas c, int start, int end,
+ @Override
+ public void drawTextRun(BaseCanvas c, int start, int end,
int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
int count = end - start;
int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 6a76c5b..e6cd798 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -32,12 +30,14 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+
+import libcore.icu.LocaleData;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
-import libcore.icu.LocaleData;
-
/**
* A widget for selecting the time of day, in either 24-hour or AM/PM mode.
* <p>
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 26e1564..6a68f60 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -16,6 +16,9 @@
package android.widget;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
import android.annotation.TestApi;
import android.content.Context;
import android.content.res.TypedArray;
@@ -29,14 +32,12 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import com.android.internal.R;
-import java.util.Calendar;
+import com.android.internal.R;
import libcore.icu.LocaleData;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+import java.util.Calendar;
/**
* A delegate implementing the basic spinner-based TimePicker.
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/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
index 69ff488..0445ebd 100644
--- a/core/java/android/widget/TwoLineListItem.java
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -20,22 +20,21 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
-import android.widget.RelativeLayout;
/**
- * <p>A view group with two children, intended for use in ListViews. This item has two
- * {@link android.widget.TextView TextViews} elements (or subclasses) with the ID values
+ * <p>A view group with two children, intended for use in ListViews. This item has two
+ * {@link android.widget.TextView TextViews} elements (or subclasses) with the ID values
* {@link android.R.id#text1 text1}
- * and {@link android.R.id#text2 text2}. There is an optional third View element with the
- * ID {@link android.R.id#selectedIcon selectedIcon}, which can be any View subclass
+ * and {@link android.R.id#text2 text2}. There is an optional third View element with the
+ * ID {@link android.R.id#selectedIcon selectedIcon}, which can be any View subclass
* (though it is typically a graphic View, such as {@link android.widget.ImageView ImageView})
- * that can be displayed when a TwoLineListItem has focus. Android supplies a
- * {@link android.R.layout#two_line_list_item standard layout resource for TwoLineListView}
+ * that can be displayed when a TwoLineListItem has focus. Android supplies a
+ * {@link android.R.layout#two_line_list_item standard layout resource for TwoLineListView}
* (which does not include a selected item icon), but you can design your own custom XML
* layout for this object.
- *
+ *
* @attr ref android.R.styleable#TwoLineListItem_mode
- *
+ *
* @deprecated This class can be implemented easily by apps using a {@link RelativeLayout}
* or a {@link LinearLayout}.
*/
@@ -51,7 +50,7 @@
}
public TwoLineListItem(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, 0);
}
public TwoLineListItem(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -70,11 +69,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
-
+
mText1 = (TextView) findViewById(com.android.internal.R.id.text1);
mText2 = (TextView) findViewById(com.android.internal.R.id.text2);
}
-
+
/**
* Returns a handle to the item with ID text1.
* @return A handle to the item with ID text1.
@@ -82,7 +81,7 @@
public TextView getText1() {
return mText1;
}
-
+
/**
* Returns a handle to the item with ID text2.
* @return A handle to the item with ID text2.
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 65af7aa..e769d71 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -21,7 +21,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
-import android.os.*;
+import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.RemoteViews.RemoteView;
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index a3f5a67..824fec8 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.Resources;
import android.icu.util.Calendar;
@@ -27,6 +25,8 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+
/**
* Displays a selectable list of years.
*/
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index fb912a4..69b79971 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -30,11 +30,11 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManager;
-import android.view.View.OnClickListener;
import android.view.WindowManager.LayoutParams;
/*
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index a5b2a91..3b6073a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -68,6 +68,7 @@
import android.widget.BaseAdapter;
import android.widget.ListView;
import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.google.android.collect.Lists;
@@ -345,6 +346,12 @@
}
@Override
+ public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE,
+ super.shouldAutoLaunchSingleChoice(target));
+ }
+
+ @Override
public void showTargetDetails(ResolveInfo ri) {
ComponentName name = ri.activityInfo.getComponentName();
boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 8c5df08..8c2c236 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -379,7 +379,7 @@
public void setState(int state, long now) {
ensureNotDead();
- if (mCurState != state) {
+ if (!mDead && (mCurState != state)) {
//Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
commitStateTime(now);
mCurState = state;
diff --git a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
new file mode 100644
index 0000000..cf1bf62
--- /dev/null
+++ b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
@@ -0,0 +1,87 @@
+/*
+ * 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.hardware;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+public class AmbientDisplayConfiguration {
+
+ private final Context mContext;
+
+ public AmbientDisplayConfiguration(Context context) {
+ mContext = context;
+ }
+
+ public boolean enabled(int user) {
+ return pulseOnNotificationEnabled(user)
+ || pulseOnPickupEnabled(user)
+ || pulseOnDoubleTapEnabled(user);
+ }
+
+ public boolean available() {
+ return pulseOnNotificationAvailable() || pulseOnPickupAvailable()
+ || pulseOnDoubleTapAvailable();
+ }
+
+ public boolean pulseOnNotificationEnabled(int user) {
+ return boolSetting(Settings.Secure.DOZE_ENABLED, user) && pulseOnNotificationAvailable();
+ }
+
+ public boolean pulseOnNotificationAvailable() {
+ return ambientDisplayAvailable();
+ }
+
+ public boolean pulseOnPickupEnabled(int user) {
+ return boolSetting(Settings.Secure.DOZE_PULSE_ON_PICK_UP, user)
+ && pulseOnPickupAvailable();
+ }
+
+ public boolean pulseOnPickupAvailable() {
+ return mContext.getResources().getBoolean(R.bool.config_dozePulsePickup)
+ && ambientDisplayAvailable();
+ }
+
+ public boolean pulseOnDoubleTapEnabled(int user) {
+ return boolSetting(Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, user)
+ && pulseOnDoubleTapAvailable();
+ }
+
+ public boolean pulseOnDoubleTapAvailable() {
+ return !TextUtils.isEmpty(doubleTapSensorType()) && ambientDisplayAvailable();
+ }
+
+ public String doubleTapSensorType() {
+ return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType);
+ }
+
+ public String ambientDisplayComponent() {
+ return mContext.getResources().getString(R.string.config_dozeComponent);
+ }
+
+ private boolean ambientDisplayAvailable() {
+ return !TextUtils.isEmpty(ambientDisplayComponent());
+ }
+
+ private boolean boolSetting(String name, int user) {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(), name, 1, user) != 0;
+ }
+
+}
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c067da7..3baccee 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -36,6 +36,8 @@
public static final String NO_SYSTEM_ERROR_CODE = "Error type 2";
public static final String NO_CLASS_ERROR_CODE = "Error type 3";
+ private String[] mRawArgs;
+
/**
* Call to run the command.
*/
@@ -45,7 +47,8 @@
return;
}
- mArgs.init(null, null, null, null, args, 0);
+ mRawArgs = args;
+ mArgs.init(null, null, null, null, args, null, 0);
try {
onRun();
@@ -109,4 +112,11 @@
public String nextArgRequired() {
return mArgs.getNextArgRequired();
}
+
+ /**
+ * Return the original raw argument list supplied to the command.
+ */
+ public String[] getRawArgs() {
+ return mRawArgs;
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0aa3a7e..b60e6b5 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -10787,7 +10787,7 @@
}
int NW = in.readInt();
- if (NW > 100) {
+ if (NW > (MAX_WAKELOCKS_PER_UID+1)) {
throw new ParcelFormatException("File corrupt: too many wake locks " + NW);
}
for (int iw = 0; iw < NW; iw++) {
@@ -10796,7 +10796,7 @@
}
int NS = in.readInt();
- if (NS > 100) {
+ if (NS > (MAX_WAKELOCKS_PER_UID+1)) {
throw new ParcelFormatException("File corrupt: too many syncs " + NS);
}
for (int is = 0; is < NS; is++) {
@@ -10805,7 +10805,7 @@
}
int NJ = in.readInt();
- if (NJ > 100) {
+ if (NJ > (MAX_WAKELOCKS_PER_UID+1)) {
throw new ParcelFormatException("File corrupt: too many job timers " + NJ);
}
for (int ij = 0; ij < NJ; ij++) {
diff --git a/core/java/com/android/internal/os/IShellCallback.aidl b/core/java/com/android/internal/os/IShellCallback.aidl
new file mode 100644
index 0000000..57d6789
--- /dev/null
+++ b/core/java/com/android/internal/os/IShellCallback.aidl
@@ -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.
+ */
+
+package com.android.internal.os;
+
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface IShellCallback {
+ ParcelFileDescriptor openOutputFile(String path, String seLinuxContext);
+}
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/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 59a3563..12d4be3 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -476,11 +476,11 @@
*/
private static PathClassLoader createSystemServerClassLoader(String systemServerClasspath,
int targetSdkVersion) {
- String librarySearchPath = System.getProperty("java.library.path");
+ String libraryPath = System.getProperty("java.library.path");
return PathClassLoaderFactory.createClassLoader(systemServerClasspath,
- librarySearchPath,
- null /* libraryPermittedPath */,
+ libraryPath,
+ libraryPath,
ClassLoader.getSystemClassLoader(),
targetSdkVersion,
true /* isNamespaceShared */);
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/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index 619303f..1abb59b 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -110,12 +110,15 @@
int statusBarColor, int navigationBarColor) {
mDecorView = decorView;
mResizingBackgroundDrawable = resizingBackgroundDrawable != null
+ && resizingBackgroundDrawable.getConstantState() != null
? resizingBackgroundDrawable.getConstantState().newDrawable()
: null;
mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null
+ && captionBackgroundDrawableDrawable.getConstantState() != null
? captionBackgroundDrawableDrawable.getConstantState().newDrawable()
: null;
mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != null
+ && userCaptionBackgroundDrawable.getConstantState() != null
? userCaptionBackgroundDrawable.getConstantState().newDrawable()
: null;
if (mCaptionBackgroundDrawable == null) {
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
new file mode 100644
index 0000000..793b228
--- /dev/null
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -0,0 +1,174 @@
+/*
+ * 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.policy;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Gravity;
+import android.view.ViewConfiguration;
+import android.widget.Scroller;
+
+import java.util.ArrayList;
+
+/**
+ * Calculates the snap targets and the snap position for the PIP given a position and a velocity.
+ * All bounds are relative to the display top/left.
+ */
+public class PipSnapAlgorithm {
+
+ // Allows snapping to the four corners
+ private static final int SNAP_MODE_CORNERS_ONLY = 0;
+ // Allows snapping to the four corners and the mid-points on the long edge in each orientation
+ private static final int SNAP_MODE_CORNERS_AND_SIDES = 1;
+ // Allows snapping to anywhere along the edge of the screen
+ private static final int SNAP_MODE_EDGE = 2;
+
+ private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
+
+ private final Context mContext;
+
+ private final ArrayList<Integer> mSnapGravities = new ArrayList<>();
+ private final int mSnapMode = SNAP_MODE_CORNERS_ONLY;
+
+ private final Scroller mScroller;
+ private final Rect mDisplayBounds = new Rect();
+ private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
+
+ public PipSnapAlgorithm(Context context, int displayId) {
+ final DisplayManager displayManager =
+ (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ final ViewConfiguration viewConfig = ViewConfiguration.get(context);
+ final Point displaySize = new Point();
+ displayManager.getDisplay(displayId).getRealSize(displaySize);
+ mContext = context;
+ mDisplayBounds.set(0, 0, displaySize.x, displaySize.y);
+ mOrientation = context.getResources().getConfiguration().orientation;
+ mScroller = new Scroller(context);
+ mScroller.setFriction(viewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
+ calculateSnapTargets();
+ }
+
+ /**
+ * @return the closest absolute snap stack bounds for the given {@param stackBounds} moving at
+ * the given {@param velocityX} and {@param velocityY}. The {@param movementBounds} should be
+ * those for the given {@param stackBounds}.
+ */
+ public Rect findClosestSnapBounds(Rect movementBounds, Rect stackBounds, float velocityX,
+ float velocityY) {
+ final Rect finalStackBounds = new Rect(stackBounds);
+ mScroller.fling(stackBounds.left, stackBounds.top,
+ (int) velocityX, (int) velocityY,
+ movementBounds.left, movementBounds.right,
+ movementBounds.top, movementBounds.bottom);
+ finalStackBounds.offsetTo(mScroller.getFinalX(), mScroller.getFinalY());
+ mScroller.abortAnimation();
+ return findClosestSnapBounds(movementBounds, finalStackBounds);
+ }
+
+ /**
+ * @return the closest absolute snap stack bounds for the given {@param stackBounds}. The
+ * {@param movementBounds} should be those for the given {@param stackBounds}.
+ */
+ public Rect findClosestSnapBounds(Rect movementBounds, Rect stackBounds) {
+ final Rect pipBounds = new Rect(movementBounds.left, movementBounds.top,
+ movementBounds.right + stackBounds.width(),
+ movementBounds.bottom + stackBounds.height());
+ final Rect newBounds = new Rect(stackBounds);
+ if (mSnapMode == SNAP_MODE_EDGE) {
+ // Find the closest edge to the given stack bounds and snap to it
+ final int fromLeft = stackBounds.left - movementBounds.left;
+ final int fromTop = stackBounds.top - movementBounds.top;
+ final int fromRight = movementBounds.right - stackBounds.left;
+ final int fromBottom = movementBounds.bottom - stackBounds.top;
+ if (fromLeft <= fromTop && fromLeft <= fromRight && fromLeft <= fromBottom) {
+ newBounds.offset(-fromLeft, 0);
+ } else if (fromTop <= fromLeft && fromTop <= fromRight && fromTop <= fromBottom) {
+ newBounds.offset(0, -fromTop);
+ } else if (fromRight < fromLeft && fromRight < fromTop && fromRight < fromBottom) {
+ newBounds.offset(fromRight, 0);
+ } else {
+ newBounds.offset(0, fromBottom);
+ }
+ } else {
+ // Find the closest snap point
+ final Rect tmpBounds = new Rect();
+ final Point[] snapTargets = new Point[mSnapGravities.size()];
+ for (int i = 0; i < mSnapGravities.size(); i++) {
+ Gravity.apply(mSnapGravities.get(i), stackBounds.width(), stackBounds.height(),
+ pipBounds, 0, 0, tmpBounds);
+ snapTargets[i] = new Point(tmpBounds.left, tmpBounds.top);
+ }
+ Point snapTarget = findClosestPoint(stackBounds.left, stackBounds.top, snapTargets);
+ newBounds.offsetTo(snapTarget.x, snapTarget.y);
+ }
+ return newBounds;
+ }
+
+ /**
+ * @return the closest point in {@param points} to the given {@param x} and {@param y}.
+ */
+ private Point findClosestPoint(int x, int y, Point[] points) {
+ Point closestPoint = null;
+ float minDistance = Float.MAX_VALUE;
+ for (Point p : points) {
+ float distance = distanceToPoint(p, x, y);
+ if (distance < minDistance) {
+ closestPoint = p;
+ minDistance = distance;
+ }
+ }
+ return closestPoint;
+ }
+
+ /**
+ * @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);
+ }
+
+ /**
+ * Calculate the snap targets for the discrete snap modes.
+ */
+ private void calculateSnapTargets() {
+ mSnapGravities.clear();
+ switch (mSnapMode) {
+ case SNAP_MODE_CORNERS_AND_SIDES:
+ if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ mSnapGravities.add(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ mSnapGravities.add(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ } else {
+ mSnapGravities.add(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ mSnapGravities.add(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ }
+ // Fall through
+ case SNAP_MODE_CORNERS_ONLY:
+ mSnapGravities.add(Gravity.TOP | Gravity.LEFT);
+ mSnapGravities.add(Gravity.TOP | Gravity.RIGHT);
+ mSnapGravities.add(Gravity.BOTTOM | Gravity.LEFT);
+ mSnapGravities.add(Gravity.BOTTOM | Gravity.RIGHT);
+ break;
+ default:
+ // Skip otherwise
+ break;
+ }
+ }
+}
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/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 1203dd2..effe219 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -59,15 +59,19 @@
private final Runnable mMovingOff = new Runnable() {
public void run() {
- mFloatingToolbarVisibilityHelper.setMoving(false);
- mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
+ if (isViewStillActive()) {
+ mFloatingToolbarVisibilityHelper.setMoving(false);
+ mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
+ }
}
};
private final Runnable mHideOff = new Runnable() {
public void run() {
- mFloatingToolbarVisibilityHelper.setHideRequested(false);
- mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
+ if (isViewStillActive()) {
+ mFloatingToolbarVisibilityHelper.setHideRequested(false);
+ mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
+ }
}
};
@@ -301,6 +305,11 @@
mOriginatingView.removeCallbacks(mHideOff);
}
+ private boolean isViewStillActive() {
+ return mOriginatingView.getWindowVisibility() == View.VISIBLE
+ && mOriginatingView.isShown();
+ }
+
/**
* A helper for showing/hiding the floating toolbar depending on certain states.
*/
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 644c7e9..4f7b106 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -580,7 +580,13 @@
return;
}
if (grantUriPermission) {
- inputContentInfo.requestPermission();
+ try {
+ inputContentInfo.requestPermission();
+ } catch (Exception e) {
+ Log.e(TAG, "InputConnectionInfo.requestPermission() failed", e);
+ args.callback.setCommitContentResult(false, args.seq);
+ return;
+ }
}
final boolean result =
ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index df9b0dd..586ece0 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -4,6 +4,7 @@
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -29,6 +30,11 @@
* the call. Only non-0 if matched is false.
*/
void onChecked(boolean matched, int throttleTimeoutMs);
+
+ /**
+ * Called when the underlying AsyncTask was cancelled.
+ */
+ default void onCancelled() {}
}
/**
@@ -61,11 +67,19 @@
final OnVerifyCallback callback) {
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
+ private List<LockPatternView.Cell> patternCopy;
+
+ @Override
+ protected void onPreExecute() {
+ // Make a copy of the pattern to prevent race conditions.
+ // No need to clone the individual cells because they are immutable.
+ patternCopy = new ArrayList(pattern);
+ }
@Override
protected byte[] doInBackground(Void... args) {
try {
- return utils.verifyPattern(pattern, challenge, userId);
+ return utils.verifyPattern(patternCopy, challenge, userId);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return null;
@@ -95,11 +109,19 @@
final OnCheckCallback callback) {
AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
private int mThrottleTimeout;
+ private List<LockPatternView.Cell> patternCopy;
+
+ @Override
+ protected void onPreExecute() {
+ // Make a copy of the pattern to prevent race conditions.
+ // No need to clone the individual cells because they are immutable.
+ patternCopy = new ArrayList(pattern);
+ }
@Override
protected Boolean doInBackground(Void... args) {
try {
- return utils.checkPattern(pattern, userId, callback::onEarlyMatched);
+ return utils.checkPattern(patternCopy, userId, callback::onEarlyMatched);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return false;
@@ -110,6 +132,11 @@
protected void onPostExecute(Boolean result) {
callback.onChecked(result, mThrottleTimeout);
}
+
+ @Override
+ protected void onCancelled() {
+ callback.onCancelled();
+ }
};
task.execute();
return task;
@@ -217,6 +244,11 @@
protected void onPostExecute(Boolean result) {
callback.onChecked(result, mThrottleTimeout);
}
+
+ @Override
+ protected void onCancelled() {
+ callback.onCancelled();
+ }
};
task.execute();
return task;
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.mk b/core/jni/Android.mk
index 4c6350b..7950685 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -19,6 +19,14 @@
LOCAL_CFLAGS += -DENABLE_CPUSETS
endif
+# TODO: Linear blending should be enabled by default, but we are
+# TODO: making it an opt-in while it's a work in progress
+# TODO: The final test should be:
+# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false)
+ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true)
+ LOCAL_CFLAGS += -DANDROID_ENABLE_LINEAR_BLENDING
+endif
+
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
@@ -214,6 +222,7 @@
LOCAL_SHARED_LIBRARIES := \
libmemtrack \
libandroidfw \
+ libbase \
libexpat \
libnativehelper \
liblog \
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 18c4ee3..467ec37 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -16,8 +16,8 @@
#include "android_util_Binder.h"
#include "android_nio_utils.h"
#include "CreateJavaOutputStreamAdaptor.h"
-#include <Caches.h>
#include <hwui/Paint.h>
+#include <hwui/Bitmap.h>
#include <renderthread/RenderProxy.h>
#include "core_jni_helpers.h"
@@ -25,374 +25,206 @@
#include <jni.h>
#include <memory>
#include <string>
-#include <sys/mman.h>
-#include <cutils/ashmem.h>
#define DEBUG_PARCEL 0
#define ASHMEM_BITMAP_MIN_SIZE (128 * (1 << 10))
+static jclass gBitmap_class;
+static jfieldID gBitmap_nativePtr;
+static jmethodID gBitmap_constructorMethodID;
+static jmethodID gBitmap_reinitMethodID;
+static jmethodID gBitmap_getAllocationByteCountMethodID;
+
namespace android {
-class WrappedPixelRef : public SkPixelRef {
+class BitmapWrapper {
public:
- WrappedPixelRef(Bitmap* wrapper, void* storage,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
- , mBitmap(*wrapper)
- , mStorage(storage) {
- reconfigure(info, rowBytes, ctable);
+ BitmapWrapper(Bitmap* bitmap)
+ : mBitmap(bitmap) { }
+
+ void freePixels() {
+ mInfo = mBitmap->info();
+ mHasHardwareMipMap = mBitmap->hasHardwareMipMap();
+ mAllocationSize = mBitmap->getAllocationByteCount();
+ mRowBytes = mBitmap->rowBytes();
+ mGenerationId = mBitmap->getGenerationID();
+ mBitmap.reset();
}
- ~WrappedPixelRef() {
- // Tell SkRefCnt that everything is as it expects by forcing
- // the refcnt to 1
- internal_dispose_restore_refcnt_to_1();
- SkSafeUnref(mColorTable);
+ bool valid() {
+ return mBitmap;
}
- void reconfigure(const SkImageInfo& newInfo, size_t rowBytes, SkColorTable* ctable) {
- if (kIndex_8_SkColorType != newInfo.colorType()) {
- ctable = nullptr;
+ Bitmap& bitmap() {
+ assertValid();
+ return *mBitmap;
+ }
+
+ void assertValid() {
+ LOG_ALWAYS_FATAL_IF(!valid(), "Error, cannot access an invalid/free'd bitmap here!");
+ }
+
+ void getSkBitmap(SkBitmap* outBitmap) {
+ assertValid();
+ mBitmap->getSkBitmap(outBitmap);
+ }
+
+ bool hasHardwareMipMap() {
+ if (mBitmap) {
+ return mBitmap->hasHardwareMipMap();
}
- mRowBytes = rowBytes;
- if (mColorTable != ctable) {
- SkSafeUnref(mColorTable);
- mColorTable = ctable;
- SkSafeRef(mColorTable);
- }
-
- // 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(mStorage, mRowBytes, mColorTable);
- }
-
- // Can't mark as override since SkPixelRef::rowBytes isn't virtual
- // but that's OK since we just want BitmapWrapper to be able to rely
- // on calling rowBytes() on an unlocked pixelref, which it will be
- // doing on a WrappedPixelRef type, not a SkPixelRef, so static
- // dispatching will do what we want.
- size_t rowBytes() const { return mRowBytes; }
- SkColorTable* colorTable() const { return mColorTable; }
-
- bool hasHardwareMipMap() const {
return mHasHardwareMipMap;
}
void setHasHardwareMipMap(bool hasMipMap) {
- mHasHardwareMipMap = hasMipMap;
+ assertValid();
+ mBitmap->setHasHardwareMipMap(hasMipMap);
}
-protected:
- virtual bool onNewLockPixels(LockRec* rec) override {
- rec->fPixels = mStorage;
- rec->fRowBytes = mRowBytes;
- rec->fColorTable = mColorTable;
- return true;
+ void setAlphaType(SkAlphaType alphaType) {
+ assertValid();
+ mBitmap->setAlphaType(alphaType);
}
- virtual void onUnlockPixels() override {
- // nothing
+ const SkImageInfo& info() {
+ if (mBitmap) {
+ return mBitmap->info();
+ }
+ return mInfo;
}
- virtual size_t getAllocatedSizeInBytes() const override {
- return info().getSafeSize(mRowBytes);
+ size_t getAllocationByteCount() const {
+ if (mBitmap) {
+ return mBitmap->getAllocationByteCount();
+ }
+ return mAllocationSize;
}
+ size_t rowBytes() const {
+ if (mBitmap) {
+ return mBitmap->rowBytes();
+ }
+ return mRowBytes;
+ }
+
+ uint32_t getGenerationID() const {
+ if (mBitmap) {
+ return mBitmap->getGenerationID();
+ }
+ return mGenerationId;
+ }
+
+ ~BitmapWrapper() { }
+
private:
- Bitmap& mBitmap;
- void* mStorage;
- size_t mRowBytes = 0;
- SkColorTable* mColorTable = nullptr;
- bool mHasHardwareMipMap = false;
-
- virtual void internal_dispose() const override {
- mBitmap.onStrongRefDestroyed();
- }
+ sk_sp<Bitmap> mBitmap;
+ SkImageInfo mInfo;
+ bool mHasHardwareMipMap;
+ size_t mAllocationSize;
+ size_t mRowBytes;
+ uint32_t mGenerationId;
};
-Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : mPixelStorageType(PixelStorageType::Java) {
- env->GetJavaVM(&mPixelStorage.java.jvm);
- mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);
- mPixelStorage.java.jstrongRef = nullptr;
- mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
- // Note: this will trigger a call to onStrongRefDestroyed(), but
- // we want the pixel ref to have a ref count of 0 at this point
- mPixelRef->unref();
-}
-
-Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : mPixelStorageType(PixelStorageType::External) {
- mPixelStorage.external.address = address;
- mPixelStorage.external.context = context;
- mPixelStorage.external.freeFunc = freeFunc;
- mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
- // Note: this will trigger a call to onStrongRefDestroyed(), but
- // we want the pixel ref to have a ref count of 0 at this point
- mPixelRef->unref();
-}
-
-Bitmap::Bitmap(void* address, int fd, size_t mappedSize,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : mPixelStorageType(PixelStorageType::Ashmem) {
- mPixelStorage.ashmem.address = address;
- mPixelStorage.ashmem.fd = fd;
- mPixelStorage.ashmem.size = mappedSize;
- mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
- // Note: this will trigger a call to onStrongRefDestroyed(), but
- // we want the pixel ref to have a ref count of 0 at this point
- mPixelRef->unref();
-}
-Bitmap::~Bitmap() {
- doFreePixels();
-}
-
-void Bitmap::freePixels() {
- AutoMutex _lock(mLock);
- if (mPinnedRefCount == 0) {
- doFreePixels();
- mPixelStorageType = PixelStorageType::Invalid;
- }
-}
-
-void Bitmap::doFreePixels() {
- switch (mPixelStorageType) {
- case PixelStorageType::Invalid:
- // already free'd, nothing to do
- break;
- 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::Java:
- JNIEnv* env = jniEnv();
- LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,
- "Deleting a bitmap wrapper while there are outstanding strong "
- "references! mPinnedRefCount = %d", mPinnedRefCount);
- env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
- break;
- }
-
- if (android::uirenderer::Caches::hasInstance()) {
- android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
- mPixelRef->getStableID());
- }
-}
-
-bool Bitmap::hasHardwareMipMap() {
- return mPixelRef->hasHardwareMipMap();
-}
-
-void Bitmap::setHasHardwareMipMap(bool hasMipMap) {
- mPixelRef->setHasHardwareMipMap(hasMipMap);
-}
-
-int Bitmap::getAshmemFd() const {
- switch (mPixelStorageType) {
- case PixelStorageType::Ashmem:
- return mPixelStorage.ashmem.fd;
- default:
- return -1;
- }
-}
-
-const SkImageInfo& Bitmap::info() const {
- return mPixelRef->info();
-}
-
-size_t Bitmap::rowBytes() const {
- return mPixelRef->rowBytes();
-}
-
-SkPixelRef* Bitmap::peekAtPixelRef() const {
- assertValid();
- return mPixelRef.get();
-}
-
-SkPixelRef* Bitmap::refPixelRef() {
- assertValid();
- android::AutoMutex _lock(mLock);
- return refPixelRefLocked();
-}
-
-SkPixelRef* Bitmap::refPixelRefLocked() {
- mPixelRef->ref();
- if (mPixelRef->unique()) {
- // We just restored this from 0, pin the pixels and inc the strong count
- // Note that there *might be* an incoming onStrongRefDestroyed from whatever
- // last unref'd
- pinPixelsLocked();
- mPinnedRefCount++;
- }
- return mPixelRef.get();
-}
-
-void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes,
- SkColorTable* ctable) {
- mPixelRef->reconfigure(info, rowBytes, ctable);
-}
-
-void Bitmap::reconfigure(const SkImageInfo& info) {
- reconfigure(info, info.minRowBytes(), nullptr);
-}
-
-void Bitmap::setAlphaType(SkAlphaType alphaType) {
- if (!SkColorTypeValidateAlphaType(info().colorType(), alphaType, &alphaType)) {
- return;
- }
-
- mPixelRef->changeAlphaType(alphaType);
-}
-
-void Bitmap::detachFromJava() {
- bool disposeSelf;
- {
- android::AutoMutex _lock(mLock);
- mAttachedToJava = false;
- disposeSelf = shouldDisposeSelfLocked();
- }
- if (disposeSelf) {
- delete this;
- }
-}
-
-bool Bitmap::shouldDisposeSelfLocked() {
- return mPinnedRefCount == 0 && !mAttachedToJava;
-}
-
-JNIEnv* Bitmap::jniEnv() {
- JNIEnv* env;
- auto success = mPixelStorage.java.jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
- LOG_ALWAYS_FATAL_IF(success != JNI_OK,
- "Failed to get JNIEnv* from JVM: %p", mPixelStorage.java.jvm);
- return env;
-}
-
-void Bitmap::onStrongRefDestroyed() {
- bool disposeSelf = false;
- {
- android::AutoMutex _lock(mLock);
- if (mPinnedRefCount > 0) {
- mPinnedRefCount--;
- if (mPinnedRefCount == 0) {
- unpinPixelsLocked();
- disposeSelf = shouldDisposeSelfLocked();
- }
- }
- }
- if (disposeSelf) {
- delete this;
- }
-}
-
-void Bitmap::pinPixelsLocked() {
- switch (mPixelStorageType) {
- case PixelStorageType::Invalid:
- LOG_ALWAYS_FATAL("Cannot pin invalid pixels!");
- break;
- case PixelStorageType::External:
- case PixelStorageType::Ashmem:
- // Nothing to do
- break;
- case PixelStorageType::Java: {
- JNIEnv* env = jniEnv();
- if (!mPixelStorage.java.jstrongRef) {
- mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>(
- env->NewGlobalRef(mPixelStorage.java.jweakRef));
- if (!mPixelStorage.java.jstrongRef) {
- LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels");
- }
- }
- break;
- }
- }
-}
-
-void Bitmap::unpinPixelsLocked() {
- switch (mPixelStorageType) {
- case PixelStorageType::Invalid:
- LOG_ALWAYS_FATAL("Cannot unpin invalid pixels!");
- break;
- case PixelStorageType::External:
- case PixelStorageType::Ashmem:
- // Don't need to do anything
- break;
- case PixelStorageType::Java: {
- JNIEnv* env = jniEnv();
- if (mPixelStorage.java.jstrongRef) {
- env->DeleteGlobalRef(mPixelStorage.java.jstrongRef);
- mPixelStorage.java.jstrongRef = nullptr;
- }
- break;
- }
- }
-}
-
-void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
- assertValid();
- android::AutoMutex _lock(mLock);
- // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes()
- // would require locking the pixels first.
- outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
- outBitmap->setPixelRef(refPixelRefLocked())->unref();
- outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
-}
-
-void Bitmap::assertValid() const {
- LOG_ALWAYS_FATAL_IF(mPixelStorageType == PixelStorageType::Invalid,
- "Error, cannot access an invalid/free'd bitmap here!");
-}
-
-} // namespace android
-
-using namespace android;
-
// Convenience class that does not take a global ref on the pixels, relying
// on the caller already having a local JNI ref
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->peekAtPixelRef()->pixels();
+ return mBitmapWrapper->bitmap().pixels();
}
bool valid() {
- return mBitmap && mBitmap->valid();
+ return mBitmapWrapper && mBitmapWrapper->valid();
}
private:
- Bitmap* mBitmap;
+ BitmapWrapper* mBitmapWrapper;
};
+namespace bitmap {
+
+// Assert that bitmap's SkAlphaType is consistent with isPremultiplied.
+static void assert_premultiplied(const SkImageInfo& info, bool isPremultiplied) {
+ // kOpaque_SkAlphaType and kIgnore_SkAlphaType mean that isPremultiplied is
+ // irrelevant. This just tests to ensure that the SkAlphaType is not
+ // opposite of isPremultiplied.
+ if (isPremultiplied) {
+ SkASSERT(info.alphaType() != kUnpremul_SkAlphaType);
+ } else {
+ SkASSERT(info.alphaType() != kPremul_SkAlphaType);
+ }
+}
+
+void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
+ bool isPremultiplied)
+{
+ // The caller needs to have already set the alpha type properly, so the
+ // native SkBitmap stays in sync with the Java Bitmap.
+ assert_premultiplied(info, isPremultiplied);
+
+ env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID,
+ info.width(), info.height(), isPremultiplied);
+}
+
+int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap)
+{
+ return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
+}
+
+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(bitmap->info(), isPremultiplied);
+ BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
+ jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
+ 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");
+ env->ExceptionDescribe();
+ }
+ return obj;
+}
+
+void toSkBitmap(jlong bitmapHandle, SkBitmap* outBitmap) {
+ LocalScopedBitmap bitmap(bitmapHandle);
+ bitmap->getSkBitmap(outBitmap);
+}
+
+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);
+ return localBitmap->bitmap();
+}
+
+Bitmap& toBitmap(JNIEnv* env, jlong bitmapHandle) {
+ SkASSERT(env);
+ LocalScopedBitmap localBitmap(bitmapHandle);
+ return localBitmap->bitmap();
+}
+
+} // namespace bitmap
+
+} // namespace android
+
+using namespace android;
+using namespace android::bitmap;
+
///////////////////////////////////////////////////////////////////////////////
// Conversions to/from SkColor, for get/setPixels, and the create method, which
// is basically like setPixels
@@ -698,8 +530,8 @@
///////////////////////////////////////////////////////////////////////////////
static int getPremulBitmapCreateFlags(bool isMutable) {
- int flags = GraphicsJNI::kBitmapCreateFlag_Premultiplied;
- if (isMutable) flags |= GraphicsJNI::kBitmapCreateFlag_Mutable;
+ int flags = android::bitmap::kBitmapCreateFlag_Premultiplied;
+ if (isMutable) flags |= android::bitmap::kBitmapCreateFlag_Mutable;
return flags;
}
@@ -721,9 +553,10 @@
}
SkBitmap bitmap;
- bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
+ bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
+ GraphicsJNI::defaultColorSpace()));
- Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
+ sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL);
if (!nativeBitmap) {
return NULL;
}
@@ -733,24 +566,22 @@
0, 0, width, height, bitmap);
}
- return GraphicsJNI::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;
- JavaPixelAllocator allocator(env);
+ SkBitmap result;
+ HeapAllocator allocator;
if (!src.copyTo(&result, dstCT, &allocator)) {
return NULL;
}
- Bitmap* bitmap = allocator.getStorageObjAndReset();
- return GraphicsJNI::createBitmap(env, bitmap,
- getPremulBitmapCreateFlags(isMutable));
+ auto bitmap = allocator.getStorageObjAndReset();
+ return createBitmap(env, bitmap, getPremulBitmapCreateFlags(isMutable));
}
static Bitmap* Bitmap_copyAshmemImpl(JNIEnv* env, SkBitmap& src, SkColorType& dstCT) {
@@ -760,31 +591,31 @@
if (!src.copyTo(&result, dstCT, &allocator)) {
return NULL;
}
- Bitmap* bitmap = allocator.getStorageObjAndReset();
- bitmap->peekAtPixelRef()->setImmutable();
+ 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();
- Bitmap* bitmap = Bitmap_copyAshmemImpl(env, src, dstCT);
- jobject ret = GraphicsJNI::createBitmap(env, bitmap, 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);
- Bitmap* bitmap = Bitmap_copyAshmemImpl(env, src, dstCT);
- jobject ret = GraphicsJNI::createBitmap(env, bitmap, getPremulBitmapCreateFlags(false));
+ auto bitmap = Bitmap_copyAshmemImpl(env, src, dstCT);
+ jobject ret = createBitmap(env, bitmap, getPremulBitmapCreateFlags(false));
return ret;
}
-static void Bitmap_destruct(Bitmap* bitmap) {
- bitmap->detachFromJava();
+static void Bitmap_destruct(BitmapWrapper* bitmap) {
+ delete bitmap;
}
static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
@@ -798,17 +629,17 @@
}
static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle,
- jint width, jint height, jint configHandle, jint allocSize,
- jboolean requestPremul) {
+ jint width, jint height, jint configHandle, jboolean requestPremul) {
LocalScopedBitmap bitmap(bitmapHandle);
+ bitmap->assertValid();
SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
// ARGB_4444 is a deprecated format, convert automatically to 8888
if (colorType == kARGB_4444_SkColorType) {
colorType = kN32_SkColorType;
}
-
- if (width * height * SkColorTypeBytesPerPixel(colorType) > allocSize) {
+ size_t requestedSize = width * height * SkColorTypeBytesPerPixel(colorType);
+ if (requestedSize > bitmap->getAllocationByteCount()) {
// done in native as there's no way to get BytesPerPixel in Java
doThrowIAE(env, "Bitmap not large enough to support new configuration");
return;
@@ -823,7 +654,8 @@
// Otherwise respect the premultiplied request.
alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
}
- bitmap->reconfigure(SkImageInfo::Make(width, height, colorType, alphaType));
+ bitmap->bitmap().reconfigure(SkImageInfo::Make(width, height, colorType, alphaType,
+ sk_sp<SkColorSpace>(bitmap->info().colorSpace())));
}
// These must match the int values in Bitmap.java
@@ -893,7 +725,7 @@
static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
- return static_cast<jint>(bitmap->peekAtPixelRef()->getGenerationID());
+ return static_cast<jint>(bitmap->getGenerationID());
}
static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) {
@@ -956,6 +788,7 @@
const bool isMutable = p->readInt32() != 0;
const SkColorType colorType = (SkColorType)p->readInt32();
const SkAlphaType alphaType = (SkAlphaType)p->readInt32();
+ const bool isSRGB = p->readInt32() != 0;
const int width = p->readInt32();
const int height = p->readInt32();
const int rowBytes = p->readInt32();
@@ -972,7 +805,8 @@
std::unique_ptr<SkBitmap> bitmap(new SkBitmap);
- if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes)) {
+ if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType,
+ isSRGB ? SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) {
return NULL;
}
@@ -1005,7 +839,7 @@
}
// Map the bitmap in place from the ashmem region if possible otherwise copy.
- Bitmap* 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 "
@@ -1026,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);
@@ -1053,7 +887,7 @@
#endif
// Copy the pixels into a new buffer.
- nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable);
+ nativeBitmap = Bitmap::allocateHeapBitmap(bitmap.get(), ctable);
SkSafeUnref(ctable);
if (!nativeBitmap) {
blob.release();
@@ -1068,7 +902,7 @@
blob.release();
}
- return GraphicsJNI::createBitmap(env, nativeBitmap,
+ return createBitmap(env, nativeBitmap.release(),
getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}
@@ -1084,12 +918,16 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
SkBitmap bitmap;
- android::Bitmap* 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();
p->writeInt32(isMutable);
p->writeInt32(bitmap.colorType());
p->writeInt32(bitmap.alphaType());
+ p->writeInt32(isSRGB); // TODO: We should write the color space (b/32072280)
p->writeInt32(bitmap.width());
p->writeInt32(bitmap.height());
p->writeInt32(bitmap.rowBytes());
@@ -1111,7 +949,7 @@
// Transfer the underlying ashmem region if we have one and it's immutable.
android::status_t status;
- int fd = androidBitmap->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 "
@@ -1161,11 +999,11 @@
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;
- JavaPixelAllocator allocator(env);
+ HeapAllocator allocator;
src.extractAlpha(&dst, paint, &allocator, &offset);
// If Skia can't allocate pixels for destination bitmap, it resets
@@ -1181,7 +1019,7 @@
env->ReleaseIntArrayElements(offsetXY, array, 0);
}
- return GraphicsJNI::createBitmap(env, allocator.getStorageObjAndReset(),
+ return createBitmap(env, allocator.getStorageObjAndReset(),
getPremulBitmapCreateFlags(true));
}
@@ -1190,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);
@@ -1211,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);
@@ -1239,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()) {
@@ -1259,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);
}
@@ -1267,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();
@@ -1282,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();
@@ -1298,11 +1136,13 @@
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()) {
+ bm0.colorType() != bm1.colorType() ||
+ bm0.alphaType() != bm1.alphaType() ||
+ bm0.colorSpace() != bm1.colorSpace()) {
return JNI_FALSE;
}
@@ -1357,20 +1197,37 @@
static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
- SkPixelRef* pixelRef = bitmap.valid() ? bitmap->peekAtPixelRef() : nullptr;
- 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) {
+ LocalScopedBitmap bitmapHandle(bitmapPtr);
+ return static_cast<jint>(bitmapHandle->getAllocationByteCount());
}
///////////////////////////////////////////////////////////////////////////////
+static jclass make_globalref(JNIEnv* env, const char classname[])
+{
+ jclass c = env->FindClass(classname);
+ SkASSERT(c);
+ return (jclass) env->NewGlobalRef(c);
+}
+
+static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
+ const char fieldname[], const char type[])
+{
+ jfieldID id = env->GetFieldID(clazz, fieldname, type);
+ SkASSERT(id);
+ return id;
+}
static const JNINativeMethod gBitmapMethods[] = {
{ "nativeCreate", "([IIIIIIZ)Landroid/graphics/Bitmap;",
@@ -1383,7 +1240,7 @@
(void*)Bitmap_copyAshmemConfig },
{ "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
{ "nativeRecycle", "(J)Z", (void*)Bitmap_recycle },
- { "nativeReconfigure", "(JIIIIZ)V", (void*)Bitmap_reconfigure },
+ { "nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure },
{ "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
(void*)Bitmap_compress },
{ "nativeErase", "(JI)V", (void*)Bitmap_erase },
@@ -1414,10 +1271,16 @@
{ "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs },
{ "nativeRefPixelRef", "(J)J", (void*)Bitmap_refPixelRef },
{ "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw },
+ { "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount },
};
int register_android_graphics_Bitmap(JNIEnv* env)
{
+ gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
+ gBitmap_nativePtr = getFieldIDCheck(env, gBitmap_class, "mNativePtr", "J");
+ gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(JIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
+ gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V");
+ gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
-}
+}
\ No newline at end of file
diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h
index aaea178..387a128 100644
--- a/core/jni/android/graphics/Bitmap.h
+++ b/core/jni/android/graphics/Bitmap.h
@@ -20,106 +20,39 @@
#include <SkBitmap.h>
#include <SkColorTable.h>
#include <SkImageInfo.h>
-#include <utils/Mutex.h>
-#include <memory>
+#include <SkPixelRef.h>
namespace android {
-enum class PixelStorageType {
- Invalid,
- External,
- Java,
- Ashmem,
+class Bitmap;
+
+namespace bitmap {
+
+enum BitmapCreateFlags {
+ kBitmapCreateFlag_None = 0x0,
+ kBitmapCreateFlag_Mutable = 0x1,
+ kBitmapCreateFlag_Premultiplied = 0x2,
};
-class WrappedPixelRef;
+jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
+ int bitmapCreateFlags, jbyteArray ninePatchChunk = NULL,
+ jobject ninePatchInsets = NULL, int density = -1);
-typedef void (*FreeFunc)(void* addr, void* context);
-/**
- * Glue-thingy that deals with managing the interaction between the Java
- * Bitmap object & SkBitmap along with trying to map a notion of strong/weak
- * lifecycles onto SkPixelRef which only has strong counts to avoid requiring
- * two GC passes to free the byte[] that backs a Bitmap.
- *
- * Since not all Bitmaps are byte[]-backed it also supports external allocations,
- * which currently is used by screenshots to wrap a gralloc buffer.
- */
-class Bitmap {
-public:
- Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
- Bitmap(void* address, void* context, FreeFunc freeFunc,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
- Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
- size_t rowBytes, SkColorTable* ctable);
+void toSkBitmap(jlong bitmapHandle, SkBitmap* outBitmap);
- const SkImageInfo& info() const;
+Bitmap& toBitmap(JNIEnv* env, jobject bitmap);
+Bitmap& toBitmap(JNIEnv* env, jlong bitmapHandle);
- // Returns nullptr if it is not backed by a jbyteArray
- jbyteArray javaByteArray() const {
- return mPixelStorageType == PixelStorageType::Java
- ? mPixelStorage.java.jstrongRef : nullptr;
- }
+/** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
+ sync with isPremultiplied
+*/
+void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
+ bool isPremultiplied);
- int width() const { return info().width(); }
- int height() const { return info().height(); }
- size_t rowBytes() const;
- SkPixelRef* peekAtPixelRef() const;
- SkPixelRef* refPixelRef();
- bool valid() const { return mPixelStorageType != PixelStorageType::Invalid; }
+int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap);
- void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
- void reconfigure(const SkImageInfo& info);
- void setAlphaType(SkAlphaType alphaType);
-
- void getSkBitmap(SkBitmap* outBitmap);
- void detachFromJava();
-
- void freePixels();
-
- bool hasHardwareMipMap();
- void setHasHardwareMipMap(bool hasMipMap);
- int getAshmemFd() const;
-
-private:
- friend class WrappedPixelRef;
-
- ~Bitmap();
- void doFreePixels();
- void onStrongRefDestroyed();
-
- void pinPixelsLocked();
- void unpinPixelsLocked();
- JNIEnv* jniEnv();
- bool shouldDisposeSelfLocked();
- void assertValid() const;
- SkPixelRef* refPixelRefLocked();
-
- android::Mutex mLock;
- int mPinnedRefCount = 0;
- std::unique_ptr<WrappedPixelRef> mPixelRef;
- PixelStorageType mPixelStorageType;
- bool mAttachedToJava = true;
-
- union {
- struct {
- void* address;
- void* context;
- FreeFunc freeFunc;
- } external;
- struct {
- void* address;
- int fd;
- size_t size;
- } ashmem;
- struct {
- JavaVM* jvm;
- jweak jweakRef;
- jbyteArray jstrongRef;
- } java;
- } mPixelStorage;
-};
+} // namespace bitmap
} // namespace android
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 77799d6..bc2da91 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -190,10 +190,11 @@
}
mBitmap->reconfigure(info, bitmap->rowBytes(), ctable);
- bitmap->setPixelRef(mBitmap->refPixelRef())->unref();
+ mBitmap->ref();
+ bitmap->setPixelRef(mBitmap)->unref();
// since we're already allocated, we lockPixels right away
- // HeapAllocator/JavaPixelAllocator behaves this way too
+ // HeapAllocator behaves this way too
bitmap->lockPixels();
return true;
}
@@ -329,17 +330,17 @@
android::Bitmap* reuseBitmap = nullptr;
unsigned int existingBufferSize = 0;
if (javaBitmap != NULL) {
- reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
- if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
+ reuseBitmap = &bitmap::toBitmap(env, javaBitmap);
+ if (reuseBitmap->isImmutable()) {
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
javaBitmap = NULL;
reuseBitmap = nullptr;
} else {
- existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
+ existingBufferSize = bitmap::getBitmapAllocationByteCount(env, javaBitmap);
}
}
- JavaPixelAllocator javaAllocator(env);
+ HeapAllocator defaultAllocator;
RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
SkBitmap::HeapAllocator heapAllocator;
@@ -353,10 +354,10 @@
decodeAllocator = &recyclingAllocator;
} else if (willScale) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
- // scaling step that copies these pixels into Java memory.
+ // scaling step.
decodeAllocator = &heapAllocator;
} else {
- decodeAllocator = &javaAllocator;
+ decodeAllocator = &defaultAllocator;
}
// Set the decode colorType. This is necessary because we can't always support
@@ -384,11 +385,8 @@
// Set the alpha type for the decode.
SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
- // Enable legacy behavior to avoid any gamma correction. Android's assets are
- // adjusted to expect a non-gamma correct premultiply.
- sk_sp<SkColorSpace> colorSpace = nullptr;
- const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
- alphaType, colorSpace);
+ const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
+ decodeColorType, alphaType, GraphicsJNI::defaultColorSpace());
SkImageInfo bitmapInfo = decodeInfo;
if (decodeColorType == kGray_8_SkColorType) {
@@ -412,7 +410,7 @@
// Use SkAndroidCodec to perform the decode.
SkAndroidCodec::AndroidOptions codecOptions;
- codecOptions.fZeroInitialized = (decodeAllocator == &javaAllocator) ?
+ codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ?
SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
codecOptions.fColorPtr = colorPtr;
codecOptions.fColorCount = colorCount;
@@ -451,8 +449,10 @@
jobject ninePatchInsets = NULL;
if (peeker.mHasInsets) {
ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
- peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
- peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
+ peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
+ peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
+ peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
+ peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
if (ninePatchInsets == NULL) {
return nullObjectReturn("nine patch insets == null");
@@ -477,7 +477,7 @@
if (javaBitmap != nullptr) {
outputAllocator = &recyclingAllocator;
} else {
- outputAllocator = &javaAllocator;
+ outputAllocator = &defaultAllocator;
}
SkColorType scaledColorType = colorTypeForScaledOutput(decodingBitmap.colorType());
@@ -494,11 +494,11 @@
}
SkPaint paint;
- // kSrc_Mode instructs us to overwrite the unininitialized pixels in
+ // kSrc_Mode instructs us to overwrite the uninitialized pixels in
// outputBitmap. Otherwise we would blend by default, which is not
// what we want.
- paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- paint.setFilterQuality(kLow_SkFilterQuality);
+ paint.setBlendMode(SkBlendMode::kSrc);
+ paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
@@ -529,18 +529,18 @@
bool isPremultiplied = !requireUnpremultiplied;
if (javaBitmap != nullptr) {
- GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
+ bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
outputBitmap.notifyPixelsChanged();
// If a java bitmap was passed in for reuse, pass it back
return javaBitmap;
}
int bitmapCreateFlags = 0x0;
- if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable;
- if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
+ if (isMutable) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Mutable;
+ if (isPremultiplied) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
// now create the java bitmap
- return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
+ return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 970001a..45bf702 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -151,23 +151,23 @@
android::Bitmap* recycledBitmap = nullptr;
size_t recycledBytes = 0;
if (javaBitmap) {
- recycledBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
- if (recycledBitmap->peekAtPixelRef()->isImmutable()) {
+ recycledBitmap = &bitmap::toBitmap(env, javaBitmap);
+ if (recycledBitmap->isImmutable()) {
ALOGW("Warning: Reusing an immutable bitmap as an image decoder target.");
}
- recycledBytes = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
+ recycledBytes = bitmap::getBitmapAllocationByteCount(env, javaBitmap);
}
// Set up the pixel allocator
SkBRDAllocator* allocator = nullptr;
RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes);
- JavaPixelAllocator javaAlloc(env);
+ HeapAllocator heapAlloc;
if (javaBitmap) {
allocator = &recycleAlloc;
// We are required to match the color type of the recycled bitmap.
colorType = recycledBitmap->info().colorType();
} else {
- allocator = &javaAlloc;
+ allocator = &heapAlloc;
}
// Decode the region.
@@ -198,9 +198,9 @@
int bitmapCreateFlags = 0;
if (!requireUnpremul) {
- bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
+ bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
}
- return GraphicsJNI::createBitmap(env, javaAlloc.getStorageObjAndReset(), bitmapCreateFlags);
+ return android::bitmap::createBitmap(env, heapAlloc.getStorageObjAndReset(), bitmapCreateFlags);
}
static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) {
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index 1a86e5f..79439e2 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -21,7 +21,6 @@
#include "SkColorFilter.h"
#include "SkColorMatrixFilter.h"
-#include "SkXfermode.h"
#include <Caches.h>
@@ -33,16 +32,16 @@
public:
static void finalizer(JNIEnv* env, jobject clazz, jlong skFilterHandle) {
SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(skFilterHandle);
- if (filter) SkSafeUnref(filter);
+ SkSafeUnref(filter);
}
static jlong CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
- SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
- return reinterpret_cast<jlong>(SkColorFilter::CreateModeFilter(srcColor, mode));
+ SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
+ return reinterpret_cast<jlong>(SkColorFilter::MakeModeFilter(srcColor, mode).release());
}
static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
- return reinterpret_cast<jlong>(SkColorMatrixFilter::CreateLightingFilter(mul, add));
+ return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release());
}
static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
@@ -50,7 +49,7 @@
const float* src = autoArray.ptr();
#ifdef SK_SCALAR_IS_FLOAT
- return reinterpret_cast<jlong>(SkColorMatrixFilter::Create(src));
+ return reinterpret_cast<jlong>(SkColorFilter::MakeMatrixFilterRowMajor255(src).release());
#else
SkASSERT(false);
#endif
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index c6a51e8..dffb63c 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -157,12 +157,6 @@
static jfieldID gPointF_xFieldID;
static jfieldID gPointF_yFieldID;
-static jclass gBitmap_class;
-static jfieldID gBitmap_nativePtr;
-static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_reinitMethodID;
-static jmethodID gBitmap_getAllocationByteCountMethodID;
-
static jclass gBitmapConfig_class;
static jfieldID gBitmapConfig_nativeInstanceID;
@@ -342,24 +336,15 @@
return static_cast<SkColorType>(gConfig2ColorType[legacyConfig]);
}
-android::Bitmap* GraphicsJNI::getBitmap(JNIEnv* env, jobject bitmap) {
- SkASSERT(env);
- SkASSERT(bitmap);
- SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
- android::Bitmap* b = reinterpret_cast<android::Bitmap*>(bitmapHandle);
- SkASSERT(b);
- return b;
-}
-
void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap) {
- getBitmap(env, bitmap)->getSkBitmap(outBitmap);
+ android::bitmap::toBitmap(env, bitmap).getSkBitmap(outBitmap);
}
-SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) {
- return getBitmap(env, bitmap)->refPixelRef();
+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);
if (NULL == jconfig) {
@@ -394,51 +379,6 @@
///////////////////////////////////////////////////////////////////////////////////////////
-// Assert that bitmap's SkAlphaType is consistent with isPremultiplied.
-static void assert_premultiplied(const SkImageInfo& info, bool isPremultiplied) {
- // kOpaque_SkAlphaType and kIgnore_SkAlphaType mean that isPremultiplied is
- // irrelevant. This just tests to ensure that the SkAlphaType is not
- // opposite of isPremultiplied.
- if (isPremultiplied) {
- SkASSERT(info.alphaType() != kUnpremul_SkAlphaType);
- } else {
- SkASSERT(info.alphaType() != kPremul_SkAlphaType);
- }
-}
-
-jobject GraphicsJNI::createBitmap(JNIEnv* env, android::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(bitmap->info(), isPremultiplied);
-
- jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
- bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
- ninePatchChunk, ninePatchInsets);
- hasException(env); // For the side effect of logging.
- return obj;
-}
-
-void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
- bool isPremultiplied)
-{
- // The caller needs to have already set the alpha type properly, so the
- // native SkBitmap stays in sync with the Java Bitmap.
- assert_premultiplied(info, isPremultiplied);
-
- env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID,
- info.width(), info.height(), isPremultiplied);
-}
-
-int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap)
-{
- return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
-}
-
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
{
SkASSERT(bitmap != NULL);
@@ -459,170 +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::Bitmap* GraphicsJNI::allocateJavaPixelRef(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 NULL;
- }
-
- // we must respect the rowBytes value already set on the bitmap instead of
- // attempting to compute our own.
- const size_t rowBytes = bitmap->rowBytes();
-
- jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
- gVMRuntime_newNonMovableArray,
- gByte_class, size);
- if (env->ExceptionCheck() != 0) {
- return NULL;
- }
- SkASSERT(arrayObj);
- jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
- if (env->ExceptionCheck() != 0) {
- return NULL;
- }
- SkASSERT(addr);
- android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
- 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::Bitmap* 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;
- }
-
- android::Bitmap* wrapper = new android::Bitmap(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::Bitmap* 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) {
@@ -644,7 +423,7 @@
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
- android::Bitmap* wrapper = new android::Bitmap(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();
@@ -656,24 +435,18 @@
return wrapper;
}
+sk_sp<SkColorSpace> GraphicsJNI::defaultColorSpace() {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+#else
+ return nullptr;
+#endif
+}
+
///////////////////////////////////////////////////////////////////////////////
-
-JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) {
- LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK,
- "env->GetJavaVM failed");
-}
-
-JavaPixelAllocator::~JavaPixelAllocator() {
- if (mStorage) {
- mStorage->detachFromJava();
- }
-}
-
-bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- JNIEnv* env = vm2env(mJavaVM);
-
- mStorage = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
- return mStorage != nullptr;
+bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
+ mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
+ return !!mStorage;
}
////////////////////////////////////////////////////////////////////////////////
@@ -722,7 +495,8 @@
// skbug.com/4538: We also need to make sure that the rowBytes on the pixel ref
// match the rowBytes on the bitmap.
bitmap->setInfo(bitmap->info(), rowBytes);
- bitmap->setPixelRef(mRecycledBitmap->refPixelRef())->unref();
+ mRecycledBitmap->ref();
+ bitmap->setPixelRef(mRecycledBitmap)->unref();
// Make sure that the recycled bitmap has the correct alpha type.
mRecycledBitmap->setAlphaType(bitmap->alphaType());
@@ -749,7 +523,8 @@
void RecyclingClippingPixelAllocator::copyIfNecessary() {
if (mNeedsCopy) {
- SkPixelRef* recycledPixels = mRecycledBitmap->refPixelRef();
+ mRecycledBitmap->ref();
+ SkPixelRef* recycledPixels = mRecycledBitmap;
void* dst = recycledPixels->pixels();
const size_t dstRowBytes = mRecycledBitmap->rowBytes();
const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(),
@@ -774,16 +549,9 @@
"env->GetJavaVM failed");
}
-AshmemPixelAllocator::~AshmemPixelAllocator() {
- if (mStorage) {
- mStorage->detachFromJava();
- }
-}
-
bool AshmemPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- JNIEnv* env = vm2env(mJavaVM);
- mStorage = GraphicsJNI::allocateAshmemPixelRef(env, bitmap, ctable);
- return mStorage != nullptr;
+ mStorage = android::Bitmap::allocateAshmemBitmap(bitmap, ctable);
+ return !!mStorage;
}
////////////////////////////////////////////////////////////////////////////////
@@ -828,11 +596,6 @@
gPointF_xFieldID = getFieldIDCheck(env, gPointF_class, "x", "F");
gPointF_yFieldID = getFieldIDCheck(env, gPointF_class, "y", "F");
- gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
- gBitmap_nativePtr = getFieldIDCheck(env, gBitmap_class, "mNativePtr", "J");
- gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
- gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V");
- gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(J)V");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 89636db..aaa8db9 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -10,8 +10,10 @@
#include "SkMallocPixelRef.h"
#include "SkPoint.h"
#include "SkRect.h"
+#include "SkColorSpace.h"
#include <jni.h>
#include <hwui/Canvas.h>
+#include <hwui/Bitmap.h>
class SkBitmapRegionDecoder;
class SkCanvas;
@@ -23,12 +25,6 @@
class GraphicsJNI {
public:
- enum BitmapCreateFlags {
- kBitmapCreateFlag_None = 0x0,
- kBitmapCreateFlag_Mutable = 0x1,
- kBitmapCreateFlag_Premultiplied = 0x2,
- };
-
// returns true if an exception is set (and dumps it out to the Log)
static bool hasException(JNIEnv*);
@@ -51,14 +47,13 @@
static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf);
static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas);
- static android::Bitmap* getBitmap(JNIEnv*, jobject bitmap);
static void getSkBitmap(JNIEnv*, jobject bitmap, SkBitmap* outBitmap);
static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap);
static SkRegion* getNativeRegion(JNIEnv*, jobject region);
// Given the 'native' long held by the Rasterizer.java object, return a
// ref to its SkRasterizer* (or NULL).
- static SkRasterizer* refNativeRasterizer(jlong rasterizerHandle);
+ static sk_sp<SkRasterizer> refNativeRasterizer(jlong rasterizerHandle);
/*
* LegacyBitmapConfig is the old enum in Skia that matched the enum int values
@@ -73,33 +68,11 @@
*/
static SkColorType getNativeBitmapColorType(JNIEnv*, jobject jconfig);
- /*
- * Create a java Bitmap object given the native bitmap
- * bitmap's SkAlphaType must already be in sync with bitmapCreateFlags.
- */
- static jobject createBitmap(JNIEnv* env, android::Bitmap* bitmap,
- int bitmapCreateFlags, jbyteArray ninePatchChunk = NULL,
- jobject ninePatchInsets = NULL, int density = -1);
-
- /** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
- sync with isPremultiplied
- */
- static void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
- bool isPremultiplied);
-
- static int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap);
-
static jobject createRegion(JNIEnv* env, SkRegion* region);
static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
- static android::Bitmap* allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable);
-
- static android::Bitmap* allocateAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable);
-
- static android::Bitmap* mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
+ static android::Bitmap* mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable, int fd, void* addr, size_t size, bool readOnly);
/**
@@ -117,17 +90,14 @@
static bool SetPixels(JNIEnv* env, jintArray colors, int srcOffset,
int srcStride, int x, int y, int width, int height,
const SkBitmap& dstBitmap);
+
+ static sk_sp<SkColorSpace> defaultColorSpace();
};
-/** Allocator which allocates the backing buffer in the Java heap.
- * Instances can only be used to perform a single allocation, which helps
- * ensure that the allocated buffer is properly accounted for with a
- * reference in the heap (or a JNI global reference).
- */
-class JavaPixelAllocator : public SkBRDAllocator {
+class HeapAllocator : public SkBRDAllocator {
public:
- explicit JavaPixelAllocator(JNIEnv* env);
- ~JavaPixelAllocator();
+ HeapAllocator() { };
+ ~HeapAllocator() { };
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override;
@@ -135,20 +105,12 @@
* Fetches the backing allocation object. Must be called!
*/
android::Bitmap* getStorageObjAndReset() {
- android::Bitmap* result = mStorage;
- mStorage = NULL;
- return result;
+ return mStorage.release();
};
- /**
- * Indicates that this allocator allocates zero initialized
- * memory.
- */
SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; }
-
private:
- JavaVM* mJavaVM;
- android::Bitmap* mStorage = nullptr;
+ sk_sp<android::Bitmap> mStorage;
};
/**
@@ -215,17 +177,15 @@
class AshmemPixelAllocator : public SkBitmap::Allocator {
public:
explicit AshmemPixelAllocator(JNIEnv* env);
- ~AshmemPixelAllocator();
+ ~AshmemPixelAllocator() { };
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable);
android::Bitmap* getStorageObjAndReset() {
- android::Bitmap* result = mStorage;
- mStorage = NULL;
- return result;
+ return mStorage.release();
};
private:
JavaVM* mJavaVM;
- android::Bitmap* mStorage = nullptr;
+ sk_sp<android::Bitmap> mStorage;
};
diff --git a/core/jni/android/graphics/MaskFilter.cpp b/core/jni/android/graphics/MaskFilter.cpp
index 2b4a1ab..3e3de5a 100644
--- a/core/jni/android/graphics/MaskFilter.cpp
+++ b/core/jni/android/graphics/MaskFilter.cpp
@@ -23,7 +23,7 @@
static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
- SkMaskFilter* filter = SkBlurMaskFilter::Create((SkBlurStyle)blurStyle, sigma);
+ SkMaskFilter* filter = SkBlurMaskFilter::Make((SkBlurStyle)blurStyle, sigma).release();
ThrowIAE_IfNull(env, filter);
return reinterpret_cast<jlong>(filter);
}
@@ -38,8 +38,8 @@
}
SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
- SkMaskFilter* filter = SkBlurMaskFilter::CreateEmboss(sigma,
- direction, ambient, specular);
+ SkMaskFilter* filter = SkBlurMaskFilter::MakeEmboss(sigma,
+ direction, ambient, specular).release();
ThrowIAE_IfNull(env, filter);
return reinterpret_cast<jlong>(filter);
}
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
index b0f3bb4..f8bb77a 100644
--- a/core/jni/android/graphics/Matrix.cpp
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -2,16 +2,16 @@
**
** Copyright 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
+** 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
+** 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
+** 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.
*/
@@ -25,14 +25,25 @@
namespace android {
+static_assert(sizeof(SkMatrix) == 40, "Unexpected sizeof(SkMatrix), "
+ "update size in Matrix.java#NATIVE_ALLOCATION_SIZE and here");
+static_assert(SK_SCALAR_IS_FLOAT, "SK_SCALAR_IS_FLOAT is false, "
+ "only float scalar is supported");
+
class SkMatrixGlue {
public:
- static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
+ // ---------------- Regular JNI -----------------------------
+
+ static void finalizer(jlong objHandle) {
SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
delete obj;
}
+ static jlong getNativeFinalizer(JNIEnv* env, jobject clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer));
+ }
+
static jlong create(JNIEnv* env, jobject clazz, jlong srcHandle) {
const SkMatrix* src = reinterpret_cast<SkMatrix*>(srcHandle);
SkMatrix* obj = new SkMatrix();
@@ -43,156 +54,39 @@
return reinterpret_cast<jlong>(obj);
}
- static jboolean isIdentity(JNIEnv* env, jobject clazz, jlong objHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- return obj->isIdentity() ? JNI_TRUE : JNI_FALSE;
- }
+ // ---------------- @FastNative -----------------------------
- static jboolean isAffine(JNIEnv* env, jobject clazz, jlong objHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- return obj->asAffine(NULL) ? JNI_TRUE : JNI_FALSE;
- }
-
- static jboolean rectStaysRect(JNIEnv* env, jobject clazz, jlong objHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- return obj->rectStaysRect() ? JNI_TRUE : JNI_FALSE;
- }
-
- static void reset(JNIEnv* env, jobject clazz, jlong objHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->reset();
- }
- static void set(JNIEnv* env, jobject clazz, jlong objHandle, jlong otherHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle);
- *obj = *other;
- }
- static void setTranslate(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setTranslate(dx, dy);
- }
- static void setScale__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sx, jfloat sy, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setScale(sx, sy, px, py);
- }
- static void setScale__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sx, jfloat sy) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setScale(sx, sy);
- }
- static void setRotate__FFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat degrees, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setRotate(degrees, px, py);
- }
- static void setRotate__F(JNIEnv* env, jobject clazz, jlong objHandle, jfloat degrees) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setRotate(degrees);
- }
- static void setSinCos__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sinValue, jfloat cosValue, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setSinCos(sinValue, cosValue, px, py);
- }
- static void setSinCos__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sinValue, jfloat cosValue) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setSinCos(sinValue, cosValue);
- }
- static void setSkew__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat kx, jfloat ky, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setSkew(kx, ky, px, py);
- }
- static void setSkew__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat kx, jfloat ky) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->setSkew(kx, ky);
- }
- static void setConcat(JNIEnv* env, jobject clazz, jlong objHandle, jlong aHandle, jlong bHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- SkMatrix* a = reinterpret_cast<SkMatrix*>(aHandle);
- SkMatrix* b = reinterpret_cast<SkMatrix*>(bHandle);
- obj->setConcat(*a, *b);
- }
-
- static void preTranslate(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preTranslate(dx, dy);
- }
-
- static void preScale__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sx, jfloat sy, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preScale(sx, sy, px, py);
- }
-
- static void preScale__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sx, jfloat sy) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preScale(sx, sy);
- }
-
- static void preRotate__FFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat degrees, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preRotate(degrees, px, py);
- }
-
- static void preRotate__F(JNIEnv* env, jobject clazz, jlong objHandle, jfloat degrees) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preRotate(degrees);
- }
-
- static void preSkew__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat kx, jfloat ky, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preSkew(kx, ky, px, py);
- }
-
- static void preSkew__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat kx, jfloat ky) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->preSkew(kx, ky);
- }
-
- static void preConcat(JNIEnv* env, jobject clazz, jlong objHandle, jlong otherHandle) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle);
- obj->preConcat(*other);
- }
-
- static void postTranslate(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->postTranslate(dx, dy);
- }
-
- static void postScale__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sx, jfloat sy, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->postScale(sx, sy, px, py);
- }
-
- static void postScale__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat sx, jfloat sy) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->postScale(sx, sy);
- }
-
- static void postRotate__FFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat degrees, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->postRotate(degrees, px, py);
- }
-
- static void postRotate__F(JNIEnv* env, jobject clazz, jlong objHandle, jfloat degrees) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->postRotate(degrees);
- }
-
- static void postSkew__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat kx, jfloat ky, jfloat px, jfloat py) {
- SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
- obj->postSkew(kx, ky, px, py);
- }
-
- static void postSkew__FF(JNIEnv* env, jobject clazz, jlong matrixHandle, jfloat kx, jfloat ky) {
+ static void mapPoints(JNIEnv* env, jobject clazz, jlong matrixHandle,
+ jfloatArray dst, jint dstIndex, jfloatArray src, jint srcIndex,
+ jint ptCount, jboolean isPts) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- matrix->postSkew(kx, ky);
+ SkASSERT(ptCount >= 0);
+ AutoJavaFloatArray autoSrc(env, src, srcIndex + (ptCount << 1),
+ kRO_JNIAccess);
+ AutoJavaFloatArray autoDst(env, dst, dstIndex + (ptCount << 1),
+ kRW_JNIAccess);
+ float* srcArray = autoSrc.ptr() + srcIndex;
+ float* dstArray = autoDst.ptr() + dstIndex;
+ if (isPts)
+ matrix->mapPoints((SkPoint*) dstArray, (const SkPoint*) srcArray,
+ ptCount);
+ else
+ matrix->mapVectors((SkVector*) dstArray, (const SkVector*) srcArray,
+ ptCount);
}
- static void postConcat(JNIEnv* env, jobject clazz, jlong matrixHandle, jlong otherHandle) {
+ static jboolean mapRect__RectFRectF(JNIEnv* env, jobject clazz,
+ jlong matrixHandle, jobjectArray dst, jobject src) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle);
- matrix->postConcat(*other);
+ SkRect dst_, src_;
+ GraphicsJNI::jrectf_to_rect(env, src, &src_);
+ jboolean rectStaysRect = matrix->mapRect(&dst_, src_);
+ GraphicsJNI::rect_to_jrectf(dst_, env, dst);
+ return rectStaysRect ? JNI_TRUE : JNI_FALSE;
}
- static jboolean setRectToRect(JNIEnv* env, jobject clazz, jlong matrixHandle, jobject src, jobject dst, jint stfHandle) {
+ static jboolean setRectToRect(JNIEnv* env, jobject clazz,
+ jlong matrixHandle, jobject src, jobject dst, jint stfHandle) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
SkMatrix::ScaleToFit stf = static_cast<SkMatrix::ScaleToFit>(stfHandle);
SkRect src_;
@@ -202,150 +96,290 @@
return matrix->setRectToRect(src_, dst_, stf) ? JNI_TRUE : JNI_FALSE;
}
- static jboolean setPolyToPoly(JNIEnv* env, jobject clazz, jlong matrixHandle,
- jfloatArray jsrc, jint srcIndex,
- jfloatArray jdst, jint dstIndex, jint ptCount) {
+ static jboolean setPolyToPoly(JNIEnv* env, jobject clazz,
+ jlong matrixHandle, jfloatArray jsrc, jint srcIndex,
+ jfloatArray jdst, jint dstIndex, jint ptCount) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
SkASSERT(srcIndex >= 0);
SkASSERT(dstIndex >= 0);
- SkASSERT((unsigned)ptCount <= 4);
+ SkASSERT((unsigned )ptCount <= 4);
- AutoJavaFloatArray autoSrc(env, jsrc, srcIndex + (ptCount << 1), kRO_JNIAccess);
- AutoJavaFloatArray autoDst(env, jdst, dstIndex + (ptCount << 1), kRW_JNIAccess);
+ AutoJavaFloatArray autoSrc(env, jsrc, srcIndex + (ptCount << 1),
+ kRO_JNIAccess);
+ AutoJavaFloatArray autoDst(env, jdst, dstIndex + (ptCount << 1),
+ kRW_JNIAccess);
float* src = autoSrc.ptr() + srcIndex;
float* dst = autoDst.ptr() + dstIndex;
bool result;
-#ifdef SK_SCALAR_IS_FLOAT
- result = matrix->setPolyToPoly((const SkPoint*)src, (const SkPoint*)dst,
- ptCount);
-#else
- SkASSERT(false);
-#endif
+ result = matrix->setPolyToPoly((const SkPoint*) src,
+ (const SkPoint*) dst, ptCount);
return result ? JNI_TRUE : JNI_FALSE;
}
- static jboolean invert(JNIEnv* env, jobject clazz, jlong matrixHandle, jlong inverseHandle) {
+ static void getValues(JNIEnv* env, jobject clazz, jlong matrixHandle,
+ jfloatArray values) {
+ SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ AutoJavaFloatArray autoValues(env, values, 9, kRW_JNIAccess);
+ float* dst = autoValues.ptr();
+ for (int i = 0; i < 9; i++) {
+ dst[i] = matrix->get(i);
+ }
+ }
+
+ static void setValues(JNIEnv* env, jobject clazz, jlong matrixHandle,
+ jfloatArray values) {
+ SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ AutoJavaFloatArray autoValues(env, values, 9, kRO_JNIAccess);
+ const float* src = autoValues.ptr();
+
+ for (int i = 0; i < 9; i++) {
+ matrix->set(i, src[i]);
+ }
+ }
+
+ // ---------------- @CriticalNative -----------------------------
+
+ static jboolean isIdentity(jlong objHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ return obj->isIdentity() ? JNI_TRUE : JNI_FALSE;
+ }
+
+ static jboolean isAffine(jlong objHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ return obj->asAffine(NULL) ? JNI_TRUE : JNI_FALSE;
+ }
+
+ static jboolean rectStaysRect(jlong objHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ return obj->rectStaysRect() ? JNI_TRUE : JNI_FALSE;
+ }
+
+ static void reset(jlong objHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->reset();
+ }
+
+ static void set(jlong objHandle, jlong otherHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle);
+ *obj = *other;
+ }
+
+ static void setTranslate(jlong objHandle, jfloat dx, jfloat dy) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setTranslate(dx, dy);
+ }
+
+ static void setScale__FFFF(jlong objHandle, jfloat sx, jfloat sy, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setScale(sx, sy, px, py);
+ }
+
+ static void setScale__FF(jlong objHandle, jfloat sx, jfloat sy) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setScale(sx, sy);
+ }
+
+ static void setRotate__FFF(jlong objHandle, jfloat degrees, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setRotate(degrees, px, py);
+ }
+
+ static void setRotate__F(jlong objHandle, jfloat degrees) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setRotate(degrees);
+ }
+
+ static void setSinCos__FFFF(jlong objHandle, jfloat sinValue,
+ jfloat cosValue, jfloat px, jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setSinCos(sinValue, cosValue, px, py);
+ }
+
+ static void setSinCos__FF(jlong objHandle, jfloat sinValue,
+ jfloat cosValue) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setSinCos(sinValue, cosValue);
+ }
+
+ static void setSkew__FFFF(jlong objHandle, jfloat kx, jfloat ky, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setSkew(kx, ky, px, py);
+ }
+
+ static void setSkew__FF(jlong objHandle, jfloat kx, jfloat ky) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->setSkew(kx, ky);
+ }
+
+ static void setConcat(jlong objHandle, jlong aHandle, jlong bHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ SkMatrix* a = reinterpret_cast<SkMatrix*>(aHandle);
+ SkMatrix* b = reinterpret_cast<SkMatrix*>(bHandle);
+ obj->setConcat(*a, *b);
+ }
+
+ static void preTranslate(jlong objHandle, jfloat dx, jfloat dy) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preTranslate(dx, dy);
+ }
+
+ static void preScale__FFFF(jlong objHandle, jfloat sx, jfloat sy, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preScale(sx, sy, px, py);
+ }
+
+ static void preScale__FF(jlong objHandle, jfloat sx, jfloat sy) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preScale(sx, sy);
+ }
+
+ static void preRotate__FFF(jlong objHandle, jfloat degrees, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preRotate(degrees, px, py);
+ }
+
+ static void preRotate__F(jlong objHandle, jfloat degrees) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preRotate(degrees);
+ }
+
+ static void preSkew__FFFF(jlong objHandle, jfloat kx, jfloat ky, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preSkew(kx, ky, px, py);
+ }
+
+ static void preSkew__FF(jlong objHandle, jfloat kx, jfloat ky) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->preSkew(kx, ky);
+ }
+
+ static void preConcat(jlong objHandle, jlong otherHandle) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle);
+ obj->preConcat(*other);
+ }
+
+ static void postTranslate(jlong objHandle, jfloat dx, jfloat dy) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->postTranslate(dx, dy);
+ }
+
+ static void postScale__FFFF(jlong objHandle, jfloat sx, jfloat sy,
+ jfloat px, jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->postScale(sx, sy, px, py);
+ }
+
+ static void postScale__FF(jlong objHandle, jfloat sx, jfloat sy) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->postScale(sx, sy);
+ }
+
+ static void postRotate__FFF(jlong objHandle, jfloat degrees, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->postRotate(degrees, px, py);
+ }
+
+ static void postRotate__F(jlong objHandle, jfloat degrees) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->postRotate(degrees);
+ }
+
+ static void postSkew__FFFF(jlong objHandle, jfloat kx, jfloat ky, jfloat px,
+ jfloat py) {
+ SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle);
+ obj->postSkew(kx, ky, px, py);
+ }
+
+ static void postSkew__FF(jlong matrixHandle, jfloat kx, jfloat ky) {
+ SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ matrix->postSkew(kx, ky);
+ }
+
+ static void postConcat(jlong matrixHandle, jlong otherHandle) {
+ SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ SkMatrix* other = reinterpret_cast<SkMatrix*>(otherHandle);
+ matrix->postConcat(*other);
+ }
+
+ static jboolean invert(jlong matrixHandle, jlong inverseHandle) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
SkMatrix* inverse = reinterpret_cast<SkMatrix*>(inverseHandle);
return matrix->invert(inverse);
}
- static void mapPoints(JNIEnv* env, jobject clazz, jlong matrixHandle,
- jfloatArray dst, jint dstIndex,
- jfloatArray src, jint srcIndex,
- jint ptCount, jboolean isPts) {
- SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- SkASSERT(ptCount >= 0);
- AutoJavaFloatArray autoSrc(env, src, srcIndex + (ptCount << 1), kRO_JNIAccess);
- AutoJavaFloatArray autoDst(env, dst, dstIndex + (ptCount << 1), kRW_JNIAccess);
- float* srcArray = autoSrc.ptr() + srcIndex;
- float* dstArray = autoDst.ptr() + dstIndex;
-#ifdef SK_SCALAR_IS_FLOAT
- if (isPts)
- matrix->mapPoints((SkPoint*)dstArray, (const SkPoint*)srcArray,
- ptCount);
- else
- matrix->mapVectors((SkVector*)dstArray, (const SkVector*)srcArray,
- ptCount);
-#else
- SkASSERT(false);
-#endif
- }
-
- static jboolean mapRect__RectFRectF(JNIEnv* env, jobject clazz, jlong matrixHandle, jobjectArray dst, jobject src) {
- SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- SkRect dst_, src_;
- GraphicsJNI::jrectf_to_rect(env, src, &src_);
- jboolean rectStaysRect = matrix->mapRect(&dst_, src_);
- GraphicsJNI::rect_to_jrectf(dst_, env, dst);
- return rectStaysRect ? JNI_TRUE : JNI_FALSE;
- }
-
- static jfloat mapRadius(JNIEnv* env, jobject clazz, jlong matrixHandle, jfloat radius) {
+ static jfloat mapRadius(jlong matrixHandle, jfloat radius) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
float result;
result = SkScalarToFloat(matrix->mapRadius(radius));
return static_cast<jfloat>(result);
}
- static void getValues(JNIEnv* env, jobject clazz, jlong matrixHandle, jfloatArray values) {
- SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- AutoJavaFloatArray autoValues(env, values, 9, kRW_JNIAccess);
- float* dst = autoValues.ptr();
-#ifdef SK_SCALAR_IS_FLOAT
- for (int i = 0; i < 9; i++) {
- dst[i] = matrix->get(i);
- }
-#else
- SkASSERT(false);
-#endif
- }
- static void setValues(JNIEnv* env, jobject clazz, jlong matrixHandle, jfloatArray values) {
- SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- AutoJavaFloatArray autoValues(env, values, 9, kRO_JNIAccess);
- const float* src = autoValues.ptr();
-
-#ifdef SK_SCALAR_IS_FLOAT
- for (int i = 0; i < 9; i++) {
- matrix->set(i, src[i]);
- }
-#else
- SkASSERT(false);
-#endif
- }
-
- static jboolean equals(JNIEnv* env, jobject clazz, jlong aHandle, jlong bHandle) {
+ static jboolean equals(jlong aHandle, jlong bHandle) {
const SkMatrix* a = reinterpret_cast<SkMatrix*>(aHandle);
const SkMatrix* b = reinterpret_cast<SkMatrix*>(bHandle);
return *a == *b;
}
- };
+};
static const JNINativeMethod methods[] = {
- {"finalizer", "(J)V", (void*) SkMatrixGlue::finalizer},
- {"native_create","(J)J", (void*) SkMatrixGlue::create},
+ {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer},
+ {"nCreate","(J)J", (void*) SkMatrixGlue::create},
- {"native_isIdentity","!(J)Z", (void*) SkMatrixGlue::isIdentity},
- {"native_isAffine","!(J)Z", (void*) SkMatrixGlue::isAffine},
- {"native_rectStaysRect","!(J)Z", (void*) SkMatrixGlue::rectStaysRect},
- {"native_reset","!(J)V", (void*) SkMatrixGlue::reset},
- {"native_set","!(JJ)V", (void*) SkMatrixGlue::set},
- {"native_setTranslate","!(JFF)V", (void*) SkMatrixGlue::setTranslate},
- {"native_setScale","!(JFFFF)V", (void*) SkMatrixGlue::setScale__FFFF},
- {"native_setScale","!(JFF)V", (void*) SkMatrixGlue::setScale__FF},
- {"native_setRotate","!(JFFF)V", (void*) SkMatrixGlue::setRotate__FFF},
- {"native_setRotate","!(JF)V", (void*) SkMatrixGlue::setRotate__F},
- {"native_setSinCos","!(JFFFF)V", (void*) SkMatrixGlue::setSinCos__FFFF},
- {"native_setSinCos","!(JFF)V", (void*) SkMatrixGlue::setSinCos__FF},
- {"native_setSkew","!(JFFFF)V", (void*) SkMatrixGlue::setSkew__FFFF},
- {"native_setSkew","!(JFF)V", (void*) SkMatrixGlue::setSkew__FF},
- {"native_setConcat","!(JJJ)V", (void*) SkMatrixGlue::setConcat},
- {"native_preTranslate","!(JFF)V", (void*) SkMatrixGlue::preTranslate},
- {"native_preScale","!(JFFFF)V", (void*) SkMatrixGlue::preScale__FFFF},
- {"native_preScale","!(JFF)V", (void*) SkMatrixGlue::preScale__FF},
- {"native_preRotate","!(JFFF)V", (void*) SkMatrixGlue::preRotate__FFF},
- {"native_preRotate","!(JF)V", (void*) SkMatrixGlue::preRotate__F},
- {"native_preSkew","!(JFFFF)V", (void*) SkMatrixGlue::preSkew__FFFF},
- {"native_preSkew","!(JFF)V", (void*) SkMatrixGlue::preSkew__FF},
- {"native_preConcat","!(JJ)V", (void*) SkMatrixGlue::preConcat},
- {"native_postTranslate","!(JFF)V", (void*) SkMatrixGlue::postTranslate},
- {"native_postScale","!(JFFFF)V", (void*) SkMatrixGlue::postScale__FFFF},
- {"native_postScale","!(JFF)V", (void*) SkMatrixGlue::postScale__FF},
- {"native_postRotate","!(JFFF)V", (void*) SkMatrixGlue::postRotate__FFF},
- {"native_postRotate","!(JF)V", (void*) SkMatrixGlue::postRotate__F},
- {"native_postSkew","!(JFFFF)V", (void*) SkMatrixGlue::postSkew__FFFF},
- {"native_postSkew","!(JFF)V", (void*) SkMatrixGlue::postSkew__FF},
- {"native_postConcat","!(JJ)V", (void*) SkMatrixGlue::postConcat},
- {"native_setRectToRect","!(JLandroid/graphics/RectF;Landroid/graphics/RectF;I)Z", (void*) SkMatrixGlue::setRectToRect},
- {"native_setPolyToPoly","!(J[FI[FII)Z", (void*) SkMatrixGlue::setPolyToPoly},
- {"native_invert","!(JJ)Z", (void*) SkMatrixGlue::invert},
- {"native_mapPoints","!(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints},
- {"native_mapRect","!(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z", (void*) SkMatrixGlue::mapRect__RectFRectF},
- {"native_mapRadius","!(JF)F", (void*) SkMatrixGlue::mapRadius},
- {"native_getValues","!(J[F)V", (void*) SkMatrixGlue::getValues},
- {"native_setValues","!(J[F)V", (void*) SkMatrixGlue::setValues},
- {"native_equals", "!(JJ)Z", (void*) SkMatrixGlue::equals}
+ // ------- @FastNative below here ---------------
+ {"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints},
+ {"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z",
+ (void*) SkMatrixGlue::mapRect__RectFRectF},
+ {"nSetRectToRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;I)Z",
+ (void*) SkMatrixGlue::setRectToRect},
+ {"nSetPolyToPoly","(J[FI[FII)Z", (void*) SkMatrixGlue::setPolyToPoly},
+ {"nGetValues","(J[F)V", (void*) SkMatrixGlue::getValues},
+ {"nSetValues","(J[F)V", (void*) SkMatrixGlue::setValues},
+
+ // ------- @CriticalNative below here ---------------
+ {"nIsIdentity","(J)Z", (void*) SkMatrixGlue::isIdentity},
+ {"nIsAffine","(J)Z", (void*) SkMatrixGlue::isAffine},
+ {"nRectStaysRect","(J)Z", (void*) SkMatrixGlue::rectStaysRect},
+ {"nReset","(J)V", (void*) SkMatrixGlue::reset},
+ {"nSet","(JJ)V", (void*) SkMatrixGlue::set},
+ {"nSetTranslate","(JFF)V", (void*) SkMatrixGlue::setTranslate},
+ {"nSetScale","(JFFFF)V", (void*) SkMatrixGlue::setScale__FFFF},
+ {"nSetScale","(JFF)V", (void*) SkMatrixGlue::setScale__FF},
+ {"nSetRotate","(JFFF)V", (void*) SkMatrixGlue::setRotate__FFF},
+ {"nSetRotate","(JF)V", (void*) SkMatrixGlue::setRotate__F},
+ {"nSetSinCos","(JFFFF)V", (void*) SkMatrixGlue::setSinCos__FFFF},
+ {"nSetSinCos","(JFF)V", (void*) SkMatrixGlue::setSinCos__FF},
+ {"nSetSkew","(JFFFF)V", (void*) SkMatrixGlue::setSkew__FFFF},
+ {"nSetSkew","(JFF)V", (void*) SkMatrixGlue::setSkew__FF},
+ {"nSetConcat","(JJJ)V", (void*) SkMatrixGlue::setConcat},
+ {"nPreTranslate","(JFF)V", (void*) SkMatrixGlue::preTranslate},
+ {"nPreScale","(JFFFF)V", (void*) SkMatrixGlue::preScale__FFFF},
+ {"nPreScale","(JFF)V", (void*) SkMatrixGlue::preScale__FF},
+ {"nPreRotate","(JFFF)V", (void*) SkMatrixGlue::preRotate__FFF},
+ {"nPreRotate","(JF)V", (void*) SkMatrixGlue::preRotate__F},
+ {"nPreSkew","(JFFFF)V", (void*) SkMatrixGlue::preSkew__FFFF},
+ {"nPreSkew","(JFF)V", (void*) SkMatrixGlue::preSkew__FF},
+ {"nPreConcat","(JJ)V", (void*) SkMatrixGlue::preConcat},
+ {"nPostTranslate","(JFF)V", (void*) SkMatrixGlue::postTranslate},
+ {"nPostScale","(JFFFF)V", (void*) SkMatrixGlue::postScale__FFFF},
+ {"nPostScale","(JFF)V", (void*) SkMatrixGlue::postScale__FF},
+ {"nPostRotate","(JFFF)V", (void*) SkMatrixGlue::postRotate__FFF},
+ {"nPostRotate","(JF)V", (void*) SkMatrixGlue::postRotate__F},
+ {"nPostSkew","(JFFFF)V", (void*) SkMatrixGlue::postSkew__FFFF},
+ {"nPostSkew","(JFF)V", (void*) SkMatrixGlue::postSkew__FF},
+ {"nPostConcat","(JJ)V", (void*) SkMatrixGlue::postConcat},
+ {"nInvert","(JJ)Z", (void*) SkMatrixGlue::invert},
+ {"nMapRadius","(JF)F", (void*) SkMatrixGlue::mapRadius},
+ {"nEquals", "(JJ)Z", (void*) SkMatrixGlue::equals}
};
static jfieldID sNativeInstanceField;
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/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 4f2f389..91e5caf 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -26,10 +26,10 @@
#include <ResourceCache.h>
#include "SkCanvas.h"
+#include "SkLatticeIter.h"
#include "SkRegion.h"
#include "GraphicsJNI.h"
-
-#include "utils/NinePatch.h"
+#include "NinePatchUtils.h"
#include "JNIHelp.h"
#include "core_jni_helpers.h"
@@ -88,18 +88,40 @@
}
static jlong getTransparentRegion(JNIEnv* env, jobject, jobject jbitmap,
- jlong chunkHandle, jobject boundsRect) {
+ jlong chunkHandle, jobject dstRect) {
Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle);
SkASSERT(chunk);
SkASSERT(boundsRect);
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
- SkRect bounds;
- GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
+ SkRect dst;
+ GraphicsJNI::jrect_to_rect(env, dstRect, &dst);
- SkRegion* region = NULL;
- NinePatch::Draw(NULL, bounds, bitmap, *chunk, NULL, ®ion);
+ SkCanvas::Lattice lattice;
+ SkIRect src = SkIRect::MakeWH(bitmap.width(), bitmap.height());
+ lattice.fBounds = &src;
+ NinePatchUtils::SetLatticeDivs(&lattice, *chunk, bitmap.width(), bitmap.height());
+ lattice.fFlags = nullptr;
+
+ SkRegion* region = nullptr;
+ if (SkLatticeIter::Valid(bitmap.width(), bitmap.height(), lattice)) {
+ SkLatticeIter iter(lattice, dst);
+ if (iter.numRectsToDraw() == chunk->numColors) {
+ SkRect dummy;
+ SkRect iterDst;
+ int index = 0;
+ while (iter.next(&dummy, &iterDst)) {
+ if (0 == chunk->getColors()[index++] && !iterDst.isEmpty()) {
+ if (!region) {
+ region = new SkRegion();
+ }
+
+ region->op(iterDst.round(), SkRegion::kUnion_Op);
+ }
+ }
+ }
+ }
return reinterpret_cast<jlong>(region);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 315dd6b..38452bb 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -29,6 +29,7 @@
#include "SkColorFilter.h"
#include "SkMaskFilter.h"
#include "SkPath.h"
+#include "SkPathEffect.h"
#include "SkRasterizer.h"
#include "SkShader.h"
#include "SkXfermode.h"
@@ -105,392 +106,80 @@
return reinterpret_cast<jlong>(obj);
}
- static void reset(JNIEnv* env, jobject clazz, jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- obj->reset();
- defaultSettingsForAndroid(obj);
- }
+ static int breakText(JNIEnv* env, const Paint& paint, Typeface* typeface, const jchar text[],
+ int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured,
+ const bool forwardScan) {
+ size_t measuredCount = 0;
+ float measured = 0;
- static void assign(JNIEnv* env, jobject clazz, jlong dstPaintHandle, jlong srcPaintHandle) {
- Paint* dst = reinterpret_cast<Paint*>(dstPaintHandle);
- const Paint* src = reinterpret_cast<Paint*>(srcPaintHandle);
- *dst = *src;
- }
+ std::unique_ptr<float[]> advancesArray(new float[count]);
+ MinikinUtils::measureText(&paint, bidiFlags, typeface, text, 0, count, count,
+ advancesArray.get());
- // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
- static const uint32_t sFilterBitmapFlag = 0x02;
-
- static jint getFlags(JNIEnv* env, jobject, jlong paintHandle) {
- Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
- uint32_t result = nativePaint->getFlags();
- result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away.
- if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) {
- result |= sFilterBitmapFlag;
+ for (int i = 0; i < count; i++) {
+ // traverse in the given direction
+ int index = forwardScan ? i : (count - i - 1);
+ float width = advancesArray[index];
+ if (measured + width > maxWidth) {
+ break;
+ }
+ // properly handle clusters when scanning backwards
+ if (forwardScan || width != 0.0f) {
+ measuredCount = i + 1;
+ }
+ measured += width;
}
- return static_cast<jint>(result);
- }
- static void setFlags(JNIEnv* env, jobject, jlong paintHandle, jint flags) {
- Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
- // Instead of modifying 0x02, change the filter level.
- nativePaint->setFilterQuality(flags & sFilterBitmapFlag
- ? kLow_SkFilterQuality
- : kNone_SkFilterQuality);
- // Don't pass through filter flag, which is no longer stored in paint's flags.
- flags &= ~sFilterBitmapFlag;
- // Use the existing value for 0x02.
- const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag;
- flags |= existing0x02Flag;
- nativePaint->setFlags(flags);
- }
-
- static jint getHinting(JNIEnv* env, jobject, jlong paintHandle) {
- return reinterpret_cast<Paint*>(paintHandle)->getHinting()
- == Paint::kNo_Hinting ? 0 : 1;
- }
-
- static void setHinting(JNIEnv* env, jobject, jlong paintHandle, jint mode) {
- reinterpret_cast<Paint*>(paintHandle)->setHinting(
- mode == 0 ? Paint::kNo_Hinting : Paint::kNormal_Hinting);
- }
-
- static void setAntiAlias(JNIEnv* env, jobject, jlong paintHandle, jboolean aa) {
- reinterpret_cast<Paint*>(paintHandle)->setAntiAlias(aa);
- }
-
- static void setLinearText(JNIEnv* env, jobject, jlong paintHandle, jboolean linearText) {
- reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText);
- }
-
- static void setSubpixelText(JNIEnv* env, jobject, jlong paintHandle, jboolean subpixelText) {
- reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText);
- }
-
- static void setUnderlineText(JNIEnv* env, jobject, jlong paintHandle, jboolean underlineText) {
- reinterpret_cast<Paint*>(paintHandle)->setUnderlineText(underlineText);
- }
-
- static void setStrikeThruText(JNIEnv* env, jobject, jlong paintHandle, jboolean strikeThruText) {
- reinterpret_cast<Paint*>(paintHandle)->setStrikeThruText(strikeThruText);
- }
-
- static void setFakeBoldText(JNIEnv* env, jobject, jlong paintHandle, jboolean fakeBoldText) {
- reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText);
- }
-
- static void setFilterBitmap(JNIEnv* env, jobject, jlong paintHandle, jboolean filterBitmap) {
- reinterpret_cast<Paint*>(paintHandle)->setFilterQuality(
- filterBitmap ? kLow_SkFilterQuality : kNone_SkFilterQuality);
- }
-
- static void setDither(JNIEnv* env, jobject, jlong paintHandle, jboolean dither) {
- reinterpret_cast<Paint*>(paintHandle)->setDither(dither);
- }
-
- static jint getStyle(JNIEnv* env, jobject clazz,jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- return static_cast<jint>(obj->getStyle());
- }
-
- static void setStyle(JNIEnv* env, jobject clazz, jlong objHandle, jint styleHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- Paint::Style style = static_cast<Paint::Style>(styleHandle);
- obj->setStyle(style);
- }
-
- static jint getColor(JNIEnv* env, jobject, jlong paintHandle) {
- int color;
- color = reinterpret_cast<Paint*>(paintHandle)->getColor();
- return static_cast<jint>(color);
- }
-
- static jint getAlpha(JNIEnv* env, jobject, jlong paintHandle) {
- int alpha;
- alpha = reinterpret_cast<Paint*>(paintHandle)->getAlpha();
- return static_cast<jint>(alpha);
- }
-
- static void setColor(JNIEnv* env, jobject, jlong paintHandle, jint color) {
- reinterpret_cast<Paint*>(paintHandle)->setColor(color);
- }
-
- static void setAlpha(JNIEnv* env, jobject, jlong paintHandle, jint a) {
- reinterpret_cast<Paint*>(paintHandle)->setAlpha(a);
- }
-
- static jfloat getStrokeWidth(JNIEnv* env, jobject, jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeWidth());
- }
-
- static void setStrokeWidth(JNIEnv* env, jobject, jlong paintHandle, jfloat width) {
- reinterpret_cast<Paint*>(paintHandle)->setStrokeWidth(width);
- }
-
- static jfloat getStrokeMiter(JNIEnv* env, jobject, jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeMiter());
- }
-
- static void setStrokeMiter(JNIEnv* env, jobject, jlong paintHandle, jfloat miter) {
- reinterpret_cast<Paint*>(paintHandle)->setStrokeMiter(miter);
- }
-
- static jint getStrokeCap(JNIEnv* env, jobject clazz, jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- return static_cast<jint>(obj->getStrokeCap());
- }
-
- static void setStrokeCap(JNIEnv* env, jobject clazz, jlong objHandle, jint capHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- Paint::Cap cap = static_cast<Paint::Cap>(capHandle);
- obj->setStrokeCap(cap);
- }
-
- static jint getStrokeJoin(JNIEnv* env, jobject clazz, jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- return static_cast<jint>(obj->getStrokeJoin());
- }
-
- static void setStrokeJoin(JNIEnv* env, jobject clazz, jlong objHandle, jint joinHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- Paint::Join join = (Paint::Join) joinHandle;
- obj->setStrokeJoin(join);
- }
-
- static jboolean getFillPath(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jlong dstHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- SkPath* src = reinterpret_cast<SkPath*>(srcHandle);
- SkPath* dst = reinterpret_cast<SkPath*>(dstHandle);
- return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
- }
-
- static jlong setShader(JNIEnv* env, jobject clazz, jlong objHandle, jlong shaderHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
- return reinterpret_cast<jlong>(obj->setShader(shader));
- }
-
- static jlong setColorFilter(JNIEnv* env, jobject clazz, jlong objHandle, jlong filterHandle) {
- Paint* obj = reinterpret_cast<Paint *>(objHandle);
- SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle);
- return reinterpret_cast<jlong>(obj->setColorFilter(filter));
- }
-
- static void setXfermode(JNIEnv* env, jobject clazz, jlong paintHandle, jint xfermodeHandle) {
- // validate that the Java enum values match our expectations
- static_assert(0 == SkXfermode::kClear_Mode, "xfermode_mismatch");
- static_assert(1 == SkXfermode::kSrc_Mode, "xfermode_mismatch");
- static_assert(2 == SkXfermode::kDst_Mode, "xfermode_mismatch");
- static_assert(3 == SkXfermode::kSrcOver_Mode, "xfermode_mismatch");
- static_assert(4 == SkXfermode::kDstOver_Mode, "xfermode_mismatch");
- static_assert(5 == SkXfermode::kSrcIn_Mode, "xfermode_mismatch");
- static_assert(6 == SkXfermode::kDstIn_Mode, "xfermode_mismatch");
- static_assert(7 == SkXfermode::kSrcOut_Mode, "xfermode_mismatch");
- static_assert(8 == SkXfermode::kDstOut_Mode, "xfermode_mismatch");
- static_assert(9 == SkXfermode::kSrcATop_Mode, "xfermode_mismatch");
- static_assert(10 == SkXfermode::kDstATop_Mode, "xfermode_mismatch");
- static_assert(11 == SkXfermode::kXor_Mode, "xfermode_mismatch");
- static_assert(16 == SkXfermode::kDarken_Mode, "xfermode_mismatch");
- static_assert(17 == SkXfermode::kLighten_Mode, "xfermode_mismatch");
- static_assert(13 == SkXfermode::kModulate_Mode, "xfermode_mismatch");
- static_assert(14 == SkXfermode::kScreen_Mode, "xfermode_mismatch");
- static_assert(12 == SkXfermode::kPlus_Mode, "xfermode_mismatch");
- static_assert(15 == SkXfermode::kOverlay_Mode, "xfermode_mismatch");
-
- SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- paint->setXfermodeMode(mode);
- }
-
- static jlong setPathEffect(JNIEnv* env, jobject clazz, jlong objHandle, jlong effectHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- SkPathEffect* effect = reinterpret_cast<SkPathEffect*>(effectHandle);
- return reinterpret_cast<jlong>(obj->setPathEffect(effect));
- }
-
- static jlong setMaskFilter(JNIEnv* env, jobject clazz, jlong objHandle, jlong maskfilterHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- SkMaskFilter* maskfilter = reinterpret_cast<SkMaskFilter*>(maskfilterHandle);
- return reinterpret_cast<jlong>(obj->setMaskFilter(maskfilter));
- }
-
- static jlong setTypeface(JNIEnv* env, jobject clazz, jlong objHandle, jlong typefaceHandle) {
- // TODO: in Paint refactoring, set typeface on android Paint, not Paint
- return NULL;
- }
-
- static jlong setRasterizer(JNIEnv* env, jobject clazz, jlong objHandle, jlong rasterizerHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- SkAutoTUnref<SkRasterizer> rasterizer(GraphicsJNI::refNativeRasterizer(rasterizerHandle));
- return reinterpret_cast<jlong>(obj->setRasterizer(rasterizer));
- }
-
- static jint getTextAlign(JNIEnv* env, jobject clazz, jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- return static_cast<jint>(obj->getTextAlign());
- }
-
- static void setTextAlign(JNIEnv* env, jobject clazz, jlong objHandle, jint alignHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- Paint::Align align = static_cast<Paint::Align>(alignHandle);
- obj->setTextAlign(align);
- }
-
- static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- ScopedUtfChars localesChars(env, locales);
- jint minikinLangListId = minikin::FontStyle::registerLanguageList(localesChars.c_str());
- obj->setMinikinLangListId(minikinLangListId);
- return minikinLangListId;
- }
-
- static void setTextLocalesByMinikinLangListId(JNIEnv* env, jobject clazz, jlong objHandle,
- jint minikinLangListId) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- obj->setMinikinLangListId(minikinLangListId);
- }
-
- static jboolean isElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle) {
- Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- return obj->getFontVariant() == minikin::VARIANT_ELEGANT;
- }
-
- static void setElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle, jboolean aa) {
- Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- obj->setFontVariant(aa ? minikin::VARIANT_ELEGANT : minikin::VARIANT_DEFAULT);
- }
-
- static jfloat getTextSize(JNIEnv* env, jobject, jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize());
- }
-
- static void setTextSize(JNIEnv* env, jobject, jlong paintHandle, jfloat textSize) {
- reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize);
- }
-
- static jfloat getTextScaleX(JNIEnv* env, jobject, jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX());
- }
-
- static void setTextScaleX(JNIEnv* env, jobject, jlong paintHandle, jfloat scaleX) {
- reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX);
- }
-
- static jfloat getTextSkewX(JNIEnv* env, jobject, jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX());
- }
-
- static void setTextSkewX(JNIEnv* env, jobject, jlong paintHandle, jfloat skewX) {
- reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX);
- }
-
- static jfloat getLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- return paint->getLetterSpacing();
- }
-
- static void setLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat letterSpacing) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- paint->setLetterSpacing(letterSpacing);
- }
-
- static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- if (!settings) {
- paint->setFontFeatureSettings(std::string());
- } else {
- ScopedUtfChars settingsChars(env, settings);
- paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+ if (jmeasured && env->GetArrayLength(jmeasured) > 0) {
+ AutoJavaFloatArray autoMeasured(env, jmeasured, 1);
+ jfloat* array = autoMeasured.ptr();
+ array[0] = measured;
}
+ return measuredCount;
}
- static jint getHyphenEdit(JNIEnv* env, jobject clazz, jlong paintHandle, jint hyphen) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- return paint->getHyphenEdit();
- }
+ static jint breakTextC(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle,
+ jcharArray jtext, jint index, jint count, jfloat maxWidth, jint bidiFlags,
+ jfloatArray jmeasuredWidth) {
+ NPE_CHECK_RETURN_ZERO(env, jtext);
- static void setHyphenEdit(JNIEnv* env, jobject clazz, jlong paintHandle, jint hyphen) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- paint->setHyphenEdit((uint32_t)hyphen);
- }
-
- static SkScalar getMetricsInternal(jlong paintHandle, jlong typefaceHandle,
- Paint::FontMetrics *metrics) {
- const int kElegantTop = 2500;
- const int kElegantBottom = -1000;
- const int kElegantAscent = 1900;
- const int kElegantDescent = -500;
- const int kElegantLeading = 0;
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- typeface = Typeface::resolveDefault(typeface);
- minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
- float saveSkewX = paint->getTextSkewX();
- bool savefakeBold = paint->isFakeBoldText();
- MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
- SkScalar spacing = paint->getFontMetrics(metrics);
- // The populateSkPaint call may have changed fake bold / text skew
- // because we want to measure with those effects applied, so now
- // restore the original settings.
- paint->setTextSkewX(saveSkewX);
- paint->setFakeBoldText(savefakeBold);
- if (paint->getFontVariant() == minikin::VARIANT_ELEGANT) {
- SkScalar size = paint->getTextSize();
- metrics->fTop = -size * kElegantTop / 2048;
- metrics->fBottom = -size * kElegantBottom / 2048;
- metrics->fAscent = -size * kElegantAscent / 2048;
- metrics->fDescent = -size * kElegantDescent / 2048;
- metrics->fLeading = size * kElegantLeading / 2048;
- spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
+
+ bool forwardTextDirection;
+ if (count < 0) {
+ forwardTextDirection = false;
+ count = -count;
}
- return spacing;
- }
-
- static jfloat ascent(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle) {
- Paint::FontMetrics metrics;
- getMetricsInternal(paintHandle, typefaceHandle, &metrics);
- return SkScalarToFloat(metrics.fAscent);
- }
-
- static jfloat descent(JNIEnv* env, jobject, jlong paintHandle, jlong typefaceHandle) {
- Paint::FontMetrics metrics;
- getMetricsInternal(paintHandle, typefaceHandle, &metrics);
- return SkScalarToFloat(metrics.fDescent);
- }
-
- static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle,
- jlong typefaceHandle, jobject metricsObj) {
- Paint::FontMetrics metrics;
- SkScalar spacing = getMetricsInternal(paintHandle, typefaceHandle, &metrics);
-
- if (metricsObj) {
- SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.top, SkScalarToFloat(metrics.fTop));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.ascent, SkScalarToFloat(metrics.fAscent));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.descent, SkScalarToFloat(metrics.fDescent));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.bottom, SkScalarToFloat(metrics.fBottom));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.leading, SkScalarToFloat(metrics.fLeading));
+ else {
+ forwardTextDirection = true;
}
- return SkScalarToFloat(spacing);
+
+ if ((index < 0) || (index + count > env->GetArrayLength(jtext))) {
+ doThrowAIOOBE(env);
+ return 0;
+ }
+
+ const jchar* text = env->GetCharArrayElements(jtext, nullptr);
+ count = breakText(env, *paint, typeface, text + index, count, maxWidth,
+ bidiFlags, jmeasuredWidth, forwardTextDirection);
+ env->ReleaseCharArrayElements(jtext, const_cast<jchar*>(text),
+ JNI_ABORT);
+ return count;
}
- static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle,
- jlong typefaceHandle, jobject metricsObj) {
- Paint::FontMetrics metrics;
+ static jint breakTextS(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jstring jtext,
+ jboolean forwards, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) {
+ NPE_CHECK_RETURN_ZERO(env, jtext);
- getMetricsInternal(paintHandle, typefaceHandle, &metrics);
- int ascent = SkScalarRoundToInt(metrics.fAscent);
- int descent = SkScalarRoundToInt(metrics.fDescent);
- int leading = SkScalarRoundToInt(metrics.fLeading);
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- if (metricsObj) {
- SkASSERT(env->IsInstanceOf(metricsObj, gFontMetricsInt_class));
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.top, SkScalarFloorToInt(metrics.fTop));
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.ascent, ascent);
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.descent, descent);
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.bottom, SkScalarCeilToInt(metrics.fBottom));
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.leading, leading);
- }
- return descent - ascent + leading;
+ int count = env->GetStringLength(jtext);
+ const jchar* text = env->GetStringChars(jtext, nullptr);
+ count = breakText(env, *paint, typeface, text, count, maxWidth, bidiFlags, jmeasuredWidth, forwards);
+ env->ReleaseStringChars(jtext, text);
+ return count;
}
static jfloat doTextAdvances(JNIEnv *env, Paint *paint, Typeface* typeface,
@@ -530,7 +219,7 @@
jint bidiFlags, jfloatArray advances, jint advancesIndex) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- jchar* textArray = env->GetCharArrayElements(text, NULL);
+ jchar* textArray = env->GetCharArrayElements(text, nullptr);
jfloat result = doTextAdvances(env, paint, typeface, textArray + contextIndex,
index - contextIndex, count, contextCount, bidiFlags, advances, advancesIndex);
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
@@ -543,7 +232,7 @@
jfloatArray advances, jint advancesIndex) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- const jchar* textArray = env->GetStringChars(text, NULL);
+ const jchar* textArray = env->GetStringChars(text, nullptr);
jfloat result = doTextAdvances(env, paint, typeface, textArray + contextStart,
start - contextStart, end - start, contextEnd - contextStart, bidiFlags,
advances, advancesIndex);
@@ -562,7 +251,7 @@
static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text,
jint contextStart, jint contextCount, jint dir, jint offset, jint cursorOpt) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- jchar* textArray = env->GetCharArrayElements(text, NULL);
+ jchar* textArray = env->GetCharArrayElements(text, nullptr);
jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, dir,
offset, cursorOpt);
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
@@ -572,7 +261,7 @@
static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, jstring text,
jint contextStart, jint contextEnd, jint dir, jint offset, jint cursorOpt) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- const jchar* textArray = env->GetStringChars(text, NULL);
+ const jchar* textArray = env->GetStringChars(text, nullptr);
jint result = doTextRunCursor(env, paint, textArray, contextStart,
contextEnd - contextStart, dir, offset, cursorOpt);
env->ReleaseStringChars(text, textArray);
@@ -635,7 +324,7 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
- const jchar* textArray = env->GetCharArrayElements(text, NULL);
+ const jchar* textArray = env->GetCharArrayElements(text, nullptr);
getTextPath(env, paint, typeface, textArray + index, count, bidiFlags, x, y, path);
env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
}
@@ -646,103 +335,11 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
- const jchar* textArray = env->GetStringChars(text, NULL);
+ const jchar* textArray = env->GetStringChars(text, nullptr);
getTextPath(env, paint, typeface, textArray + start, end - start, bidiFlags, x, y, path);
env->ReleaseStringChars(text, textArray);
}
- static void setShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius,
- jfloat dx, jfloat dy, jint color) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- if (radius <= 0) {
- paint->setLooper(NULL);
- }
- else {
- SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
- paint->setLooper(SkBlurDrawLooper::Create((SkColor)color, sigma, dx, dy))->unref();
- }
- }
-
- static jboolean hasShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- return paint->getLooper() && paint->getLooper()->asABlurShadow(NULL);
- }
-
- static int breakText(JNIEnv* env, const Paint& paint, Typeface* typeface, const jchar text[],
- int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured,
- const bool forwardScan) {
- size_t measuredCount = 0;
- float measured = 0;
-
- std::unique_ptr<float[]> advancesArray(new float[count]);
- MinikinUtils::measureText(&paint, bidiFlags, typeface, text, 0, count, count,
- advancesArray.get());
-
- for (int i = 0; i < count; i++) {
- // traverse in the given direction
- int index = forwardScan ? i : (count - i - 1);
- float width = advancesArray[index];
- if (measured + width > maxWidth) {
- break;
- }
- // properly handle clusters when scanning backwards
- if (forwardScan || width != 0.0f) {
- measuredCount = i + 1;
- }
- measured += width;
- }
-
- if (jmeasured && env->GetArrayLength(jmeasured) > 0) {
- AutoJavaFloatArray autoMeasured(env, jmeasured, 1);
- jfloat* array = autoMeasured.ptr();
- array[0] = measured;
- }
- return measuredCount;
- }
-
- static jint breakTextC(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jcharArray jtext,
- jint index, jint count, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) {
- NPE_CHECK_RETURN_ZERO(env, jtext);
-
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
-
- bool forwardTextDirection;
- if (count < 0) {
- forwardTextDirection = false;
- count = -count;
- }
- else {
- forwardTextDirection = true;
- }
-
- if ((index < 0) || (index + count > env->GetArrayLength(jtext))) {
- doThrowAIOOBE(env);
- return 0;
- }
-
- const jchar* text = env->GetCharArrayElements(jtext, NULL);
- count = breakText(env, *paint, typeface, text + index, count, maxWidth,
- bidiFlags, jmeasuredWidth, forwardTextDirection);
- env->ReleaseCharArrayElements(jtext, const_cast<jchar*>(text),
- JNI_ABORT);
- return count;
- }
-
- static jint breakTextS(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jstring jtext,
- jboolean forwards, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) {
- NPE_CHECK_RETURN_ZERO(env, jtext);
-
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
-
- int count = env->GetStringLength(jtext);
- const jchar* text = env->GetStringChars(jtext, NULL);
- count = breakText(env, *paint, typeface, text, count, maxWidth, bidiFlags, jmeasuredWidth, forwards);
- env->ReleaseStringChars(jtext, text);
- return count;
- }
-
static void doTextBounds(JNIEnv* env, const jchar* text, int count, jobject bounds,
const Paint& paint, Typeface* typeface, jint bidiFlags) {
SkRect r;
@@ -764,7 +361,7 @@
jstring text, jint start, jint end, jint bidiFlags, jobject bounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- const jchar* textArray = env->GetStringChars(text, NULL);
+ const jchar* textArray = env->GetStringChars(text, nullptr);
doTextBounds(env, textArray + start, end - start, bounds, *paint, typeface, bidiFlags);
env->ReleaseStringChars(text, textArray);
}
@@ -773,12 +370,28 @@
jcharArray text, jint index, jint count, jint bidiFlags, jobject bounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- const jchar* textArray = env->GetCharArrayElements(text, NULL);
+ const jchar* textArray = env->GetCharArrayElements(text, nullptr);
doTextBounds(env, textArray + index, count, bounds, *paint, typeface, bidiFlags);
env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
JNI_ABORT);
}
+ // Returns true if the given string is exact one pair of regional indicators.
+ static bool isFlag(const jchar* str, size_t length) {
+ const jchar RI_LEAD_SURROGATE = 0xD83C;
+ const jchar RI_TRAIL_SURROGATE_MIN = 0xDDE6;
+ const jchar RI_TRAIL_SURROGATE_MAX = 0xDDFF;
+
+ if (length != 4) {
+ return false;
+ }
+ if (str[0] != RI_LEAD_SURROGATE || str[2] != RI_LEAD_SURROGATE) {
+ return false;
+ }
+ return RI_TRAIL_SURROGATE_MIN <= str[1] && str[1] <= RI_TRAIL_SURROGATE_MAX &&
+ RI_TRAIL_SURROGATE_MIN <= str[3] && str[3] <= RI_TRAIL_SURROGATE_MAX;
+ }
+
static jboolean layoutContainsNotdef(const minikin::Layout& layout) {
for (size_t i = 0; i < layout.nGlyphs(); i++) {
if (layout.getGlyphId(i) == 0) {
@@ -803,22 +416,6 @@
return count;
}
- // Returns true if the given string is exact one pair of regional indicators.
- static bool isFlag(const jchar* str, size_t length) {
- const jchar RI_LEAD_SURROGATE = 0xD83C;
- const jchar RI_TRAIL_SURROGATE_MIN = 0xDDE6;
- const jchar RI_TRAIL_SURROGATE_MAX = 0xDDFF;
-
- if (length != 4) {
- return false;
- }
- if (str[0] != RI_LEAD_SURROGATE || str[2] != RI_LEAD_SURROGATE) {
- return false;
- }
- return RI_TRAIL_SURROGATE_MIN <= str[1] && str[1] <= RI_TRAIL_SURROGATE_MAX &&
- RI_TRAIL_SURROGATE_MIN <= str[3] && str[3] <= RI_TRAIL_SURROGATE_MAX;
- }
-
static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle,
jint bidiFlags, jstring string) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
@@ -913,7 +510,7 @@
jint contextEnd, jboolean isRtl, jint offset) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, NULL);
+ jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, nullptr);
jfloat result = doRunAdvance(paint, typeface, textArray + contextStart,
start - contextStart, end - start, contextEnd - contextStart, isRtl,
offset - contextStart);
@@ -935,7 +532,7 @@
jint contextEnd, jboolean isRtl, jfloat advance) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
- jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, NULL);
+ jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, nullptr);
jint result = doOffsetForAdvance(paint, typeface, textArray + contextStart,
start - contextStart, end - start, contextEnd - contextStart, isRtl, advance);
result += contextStart;
@@ -943,76 +540,426 @@
return result;
}
+ // ------------------ @FastNative ---------------------------
+
+ static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ ScopedUtfChars localesChars(env, locales);
+ jint minikinLangListId = minikin::FontStyle::registerLanguageList(localesChars.c_str());
+ obj->setMinikinLangListId(minikinLangListId);
+ return minikinLangListId;
+ }
+
+ static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (!settings) {
+ paint->setFontFeatureSettings(std::string());
+ } else {
+ ScopedUtfChars settingsChars(env, settings);
+ paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+ }
+ }
+
+ static SkScalar getMetricsInternal(jlong paintHandle, jlong typefaceHandle,
+ Paint::FontMetrics *metrics) {
+ const int kElegantTop = 2500;
+ const int kElegantBottom = -1000;
+ const int kElegantAscent = 1900;
+ const int kElegantDescent = -500;
+ const int kElegantLeading = 0;
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle);
+ typeface = Typeface::resolveDefault(typeface);
+ minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
+ float saveSkewX = paint->getTextSkewX();
+ bool savefakeBold = paint->isFakeBoldText();
+ MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
+ SkScalar spacing = paint->getFontMetrics(metrics);
+ // The populateSkPaint call may have changed fake bold / text skew
+ // because we want to measure with those effects applied, so now
+ // restore the original settings.
+ paint->setTextSkewX(saveSkewX);
+ paint->setFakeBoldText(savefakeBold);
+ if (paint->getFontVariant() == minikin::VARIANT_ELEGANT) {
+ SkScalar size = paint->getTextSize();
+ metrics->fTop = -size * kElegantTop / 2048;
+ metrics->fBottom = -size * kElegantBottom / 2048;
+ metrics->fAscent = -size * kElegantAscent / 2048;
+ metrics->fDescent = -size * kElegantDescent / 2048;
+ metrics->fLeading = size * kElegantLeading / 2048;
+ spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
+ }
+ return spacing;
+ }
+
+ static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle,
+ jlong typefaceHandle, jobject metricsObj) {
+ Paint::FontMetrics metrics;
+ SkScalar spacing = getMetricsInternal(paintHandle, typefaceHandle, &metrics);
+
+ if (metricsObj) {
+ SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
+ env->SetFloatField(metricsObj, gFontMetrics_fieldID.top, SkScalarToFloat(metrics.fTop));
+ env->SetFloatField(metricsObj, gFontMetrics_fieldID.ascent, SkScalarToFloat(metrics.fAscent));
+ env->SetFloatField(metricsObj, gFontMetrics_fieldID.descent, SkScalarToFloat(metrics.fDescent));
+ env->SetFloatField(metricsObj, gFontMetrics_fieldID.bottom, SkScalarToFloat(metrics.fBottom));
+ env->SetFloatField(metricsObj, gFontMetrics_fieldID.leading, SkScalarToFloat(metrics.fLeading));
+ }
+ return SkScalarToFloat(spacing);
+ }
+
+ static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle,
+ jlong typefaceHandle, jobject metricsObj) {
+ Paint::FontMetrics metrics;
+
+ getMetricsInternal(paintHandle, typefaceHandle, &metrics);
+ int ascent = SkScalarRoundToInt(metrics.fAscent);
+ int descent = SkScalarRoundToInt(metrics.fDescent);
+ int leading = SkScalarRoundToInt(metrics.fLeading);
+
+ if (metricsObj) {
+ SkASSERT(env->IsInstanceOf(metricsObj, gFontMetricsInt_class));
+ env->SetIntField(metricsObj, gFontMetricsInt_fieldID.top, SkScalarFloorToInt(metrics.fTop));
+ env->SetIntField(metricsObj, gFontMetricsInt_fieldID.ascent, ascent);
+ env->SetIntField(metricsObj, gFontMetricsInt_fieldID.descent, descent);
+ env->SetIntField(metricsObj, gFontMetricsInt_fieldID.bottom, SkScalarCeilToInt(metrics.fBottom));
+ env->SetIntField(metricsObj, gFontMetricsInt_fieldID.leading, leading);
+ }
+ return descent - ascent + leading;
+ }
+
+
+ // ------------------ @CriticalNative ---------------------------
+
+ static void reset(jlong objHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ obj->reset();
+ defaultSettingsForAndroid(obj);
+ }
+
+ static void assign(jlong dstPaintHandle, jlong srcPaintHandle) {
+ Paint* dst = reinterpret_cast<Paint*>(dstPaintHandle);
+ const Paint* src = reinterpret_cast<Paint*>(srcPaintHandle);
+ *dst = *src;
+ }
+
+ // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
+ static const uint32_t sFilterBitmapFlag = 0x02;
+
+ static jint getFlags(jlong paintHandle) {
+ Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
+ uint32_t result = nativePaint->getFlags();
+ result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away.
+ if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) {
+ result |= sFilterBitmapFlag;
+ }
+ return static_cast<jint>(result);
+ }
+
+ static void setFlags(jlong paintHandle, jint flags) {
+ Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
+ // Instead of modifying 0x02, change the filter level.
+ nativePaint->setFilterQuality(flags & sFilterBitmapFlag
+ ? kLow_SkFilterQuality
+ : kNone_SkFilterQuality);
+ // Don't pass through filter flag, which is no longer stored in paint's flags.
+ flags &= ~sFilterBitmapFlag;
+ // Use the existing value for 0x02.
+ const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag;
+ flags |= existing0x02Flag;
+ nativePaint->setFlags(flags);
+ }
+
+ static jint getHinting(jlong paintHandle) {
+ return reinterpret_cast<Paint*>(paintHandle)->getHinting()
+ == Paint::kNo_Hinting ? 0 : 1;
+ }
+
+ static void setHinting(jlong paintHandle, jint mode) {
+ reinterpret_cast<Paint*>(paintHandle)->setHinting(
+ mode == 0 ? Paint::kNo_Hinting : Paint::kNormal_Hinting);
+ }
+
+ static void setAntiAlias(jlong paintHandle, jboolean aa) {
+ reinterpret_cast<Paint*>(paintHandle)->setAntiAlias(aa);
+ }
+
+ static void setLinearText(jlong paintHandle, jboolean linearText) {
+ reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText);
+ }
+
+ static void setSubpixelText(jlong paintHandle, jboolean subpixelText) {
+ reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText);
+ }
+
+ static void setUnderlineText(jlong paintHandle, jboolean underlineText) {
+ reinterpret_cast<Paint*>(paintHandle)->setUnderlineText(underlineText);
+ }
+
+ static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) {
+ reinterpret_cast<Paint*>(paintHandle)->setStrikeThruText(strikeThruText);
+ }
+
+ static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) {
+ reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText);
+ }
+
+ static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) {
+ reinterpret_cast<Paint*>(paintHandle)->setFilterQuality(
+ filterBitmap ? kLow_SkFilterQuality : kNone_SkFilterQuality);
+ }
+
+ static void setDither(jlong paintHandle, jboolean dither) {
+ reinterpret_cast<Paint*>(paintHandle)->setDither(dither);
+ }
+
+ static jint getStyle(jlong objHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ return static_cast<jint>(obj->getStyle());
+ }
+
+ static void setStyle(jlong objHandle, jint styleHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ Paint::Style style = static_cast<Paint::Style>(styleHandle);
+ obj->setStyle(style);
+ }
+
+ static jint getColor(jlong paintHandle) {
+ int color;
+ color = reinterpret_cast<Paint*>(paintHandle)->getColor();
+ return static_cast<jint>(color);
+ }
+
+ static jint getAlpha(jlong paintHandle) {
+ int alpha;
+ alpha = reinterpret_cast<Paint*>(paintHandle)->getAlpha();
+ return static_cast<jint>(alpha);
+ }
+
+ static void setColor(jlong paintHandle, jint color) {
+ reinterpret_cast<Paint*>(paintHandle)->setColor(color);
+ }
+
+ static void setAlpha(jlong paintHandle, jint a) {
+ reinterpret_cast<Paint*>(paintHandle)->setAlpha(a);
+ }
+
+ static jfloat getStrokeWidth(jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeWidth());
+ }
+
+ static void setStrokeWidth(jlong paintHandle, jfloat width) {
+ reinterpret_cast<Paint*>(paintHandle)->setStrokeWidth(width);
+ }
+
+ static jfloat getStrokeMiter(jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getStrokeMiter());
+ }
+
+ static void setStrokeMiter(jlong paintHandle, jfloat miter) {
+ reinterpret_cast<Paint*>(paintHandle)->setStrokeMiter(miter);
+ }
+
+ static jint getStrokeCap(jlong objHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ return static_cast<jint>(obj->getStrokeCap());
+ }
+
+ static void setStrokeCap(jlong objHandle, jint capHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ Paint::Cap cap = static_cast<Paint::Cap>(capHandle);
+ obj->setStrokeCap(cap);
+ }
+
+ static jint getStrokeJoin(jlong objHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ return static_cast<jint>(obj->getStrokeJoin());
+ }
+
+ static void setStrokeJoin(jlong objHandle, jint joinHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ Paint::Join join = (Paint::Join) joinHandle;
+ obj->setStrokeJoin(join);
+ }
+
+ static jboolean getFillPath(jlong objHandle, jlong srcHandle, jlong dstHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ SkPath* src = reinterpret_cast<SkPath*>(srcHandle);
+ SkPath* dst = reinterpret_cast<SkPath*>(dstHandle);
+ return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
+ }
+
+ static jlong setShader(jlong objHandle, jlong shaderHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
+ obj->setShader(sk_ref_sp(shader));
+ return reinterpret_cast<jlong>(obj->getShader());
+ }
+
+ static jlong setColorFilter(jlong objHandle, jlong filterHandle) {
+ Paint* obj = reinterpret_cast<Paint *>(objHandle);
+ SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle);
+ obj->setColorFilter(sk_ref_sp(filter));
+ return reinterpret_cast<jlong>(obj->getColorFilter());
+ }
+
+ static void setXfermode(jlong paintHandle, jint xfermodeHandle) {
+ // validate that the Java enum values match our expectations
+ static_assert(0 == static_cast<int>(SkBlendMode::kClear), "xfermode_mismatch");
+ static_assert(1 == static_cast<int>(SkBlendMode::kSrc), "xfermode_mismatch");
+ static_assert(2 == static_cast<int>(SkBlendMode::kDst), "xfermode_mismatch");
+ static_assert(3 == static_cast<int>(SkBlendMode::kSrcOver), "xfermode_mismatch");
+ static_assert(4 == static_cast<int>(SkBlendMode::kDstOver), "xfermode_mismatch");
+ static_assert(5 == static_cast<int>(SkBlendMode::kSrcIn), "xfermode_mismatch");
+ static_assert(6 == static_cast<int>(SkBlendMode::kDstIn), "xfermode_mismatch");
+ static_assert(7 == static_cast<int>(SkBlendMode::kSrcOut), "xfermode_mismatch");
+ static_assert(8 == static_cast<int>(SkBlendMode::kDstOut), "xfermode_mismatch");
+ static_assert(9 == static_cast<int>(SkBlendMode::kSrcATop), "xfermode_mismatch");
+ static_assert(10 == static_cast<int>(SkBlendMode::kDstATop), "xfermode_mismatch");
+ static_assert(11 == static_cast<int>(SkBlendMode::kXor), "xfermode_mismatch");
+ static_assert(16 == static_cast<int>(SkBlendMode::kDarken), "xfermode_mismatch");
+ static_assert(17 == static_cast<int>(SkBlendMode::kLighten), "xfermode_mismatch");
+ static_assert(13 == static_cast<int>(SkBlendMode::kModulate), "xfermode_mismatch");
+ static_assert(14 == static_cast<int>(SkBlendMode::kScreen), "xfermode_mismatch");
+ static_assert(12 == static_cast<int>(SkBlendMode::kPlus), "xfermode_mismatch");
+ static_assert(15 == static_cast<int>(SkBlendMode::kOverlay), "xfermode_mismatch");
+
+ SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ paint->setBlendMode(mode);
+ }
+
+ static jlong setPathEffect(jlong objHandle, jlong effectHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ SkPathEffect* effect = reinterpret_cast<SkPathEffect*>(effectHandle);
+ obj->setPathEffect(sk_ref_sp(effect));
+ return reinterpret_cast<jlong>(obj->getPathEffect());
+ }
+
+ static jlong setMaskFilter(jlong objHandle, jlong maskfilterHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ SkMaskFilter* maskfilter = reinterpret_cast<SkMaskFilter*>(maskfilterHandle);
+ obj->setMaskFilter(sk_ref_sp(maskfilter));
+ return reinterpret_cast<jlong>(obj->getMaskFilter());
+ }
+
+ static jlong setTypeface(jlong objHandle, jlong typefaceHandle) {
+ // TODO: in Paint refactoring, set typeface on android Paint, not Paint
+ return 0;
+ }
+
+ static jlong setRasterizer(jlong objHandle, jlong rasterizerHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ obj->setRasterizer(GraphicsJNI::refNativeRasterizer(rasterizerHandle));
+ return reinterpret_cast<jlong>(obj->getRasterizer());
+ }
+
+ static jint getTextAlign(jlong objHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ return static_cast<jint>(obj->getTextAlign());
+ }
+
+ static void setTextAlign(jlong objHandle, jint alignHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ Paint::Align align = static_cast<Paint::Align>(alignHandle);
+ obj->setTextAlign(align);
+ }
+
+ static void setTextLocalesByMinikinLangListId(jlong objHandle,
+ jint minikinLangListId) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ obj->setMinikinLangListId(minikinLangListId);
+ }
+
+ static jboolean isElegantTextHeight(jlong paintHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(paintHandle);
+ return obj->getFontVariant() == minikin::VARIANT_ELEGANT;
+ }
+
+ static void setElegantTextHeight(jlong paintHandle, jboolean aa) {
+ Paint* obj = reinterpret_cast<Paint*>(paintHandle);
+ obj->setFontVariant(aa ? minikin::VARIANT_ELEGANT : minikin::VARIANT_DEFAULT);
+ }
+
+ static jfloat getTextSize(jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize());
+ }
+
+ static void setTextSize(jlong paintHandle, jfloat textSize) {
+ reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize);
+ }
+
+ static jfloat getTextScaleX(jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX());
+ }
+
+ static void setTextScaleX(jlong paintHandle, jfloat scaleX) {
+ reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX);
+ }
+
+ static jfloat getTextSkewX(jlong paintHandle) {
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX());
+ }
+
+ static void setTextSkewX(jlong paintHandle, jfloat skewX) {
+ reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX);
+ }
+
+ static jfloat getLetterSpacing(jlong paintHandle) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ return paint->getLetterSpacing();
+ }
+
+ static void setLetterSpacing(jlong paintHandle, jfloat letterSpacing) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ paint->setLetterSpacing(letterSpacing);
+ }
+
+ static jint getHyphenEdit(jlong paintHandle, jint hyphen) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ return paint->getHyphenEdit();
+ }
+
+ static void setHyphenEdit(jlong paintHandle, jint hyphen) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ paint->setHyphenEdit((uint32_t)hyphen);
+ }
+
+ static jfloat ascent(jlong paintHandle, jlong typefaceHandle) {
+ Paint::FontMetrics metrics;
+ getMetricsInternal(paintHandle, typefaceHandle, &metrics);
+ return SkScalarToFloat(metrics.fAscent);
+ }
+
+ static jfloat descent(jlong paintHandle, jlong typefaceHandle) {
+ Paint::FontMetrics metrics;
+ getMetricsInternal(paintHandle, typefaceHandle, &metrics);
+ return SkScalarToFloat(metrics.fDescent);
+ }
+
+ static void setShadowLayer(jlong paintHandle, jfloat radius,
+ jfloat dx, jfloat dy, jint color) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (radius <= 0) {
+ paint->setLooper(nullptr);
+ }
+ else {
+ SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
+ paint->setLooper(SkBlurDrawLooper::Make((SkColor)color, sigma, dx, dy));
+ }
+ }
+
+ static jboolean hasShadowLayer(jlong paintHandle) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
+ }
+
}; // namespace PaintGlue
static const JNINativeMethod methods[] = {
{"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer},
{"nInit","()J", (void*) PaintGlue::init},
{"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
-
- {"nReset","!(J)V", (void*) PaintGlue::reset},
- {"nSet","!(JJ)V", (void*) PaintGlue::assign},
- {"nGetFlags","!(J)I", (void*) PaintGlue::getFlags},
- {"nSetFlags","!(JI)V", (void*) PaintGlue::setFlags},
- {"nGetHinting","!(J)I", (void*) PaintGlue::getHinting},
- {"nSetHinting","!(JI)V", (void*) PaintGlue::setHinting},
- {"nSetAntiAlias","!(JZ)V", (void*) PaintGlue::setAntiAlias},
- {"nSetSubpixelText","!(JZ)V", (void*) PaintGlue::setSubpixelText},
- {"nSetLinearText","!(JZ)V", (void*) PaintGlue::setLinearText},
- {"nSetUnderlineText","!(JZ)V", (void*) PaintGlue::setUnderlineText},
- {"nSetStrikeThruText","!(JZ)V", (void*) PaintGlue::setStrikeThruText},
- {"nSetFakeBoldText","!(JZ)V", (void*) PaintGlue::setFakeBoldText},
- {"nSetFilterBitmap","!(JZ)V", (void*) PaintGlue::setFilterBitmap},
- {"nSetDither","!(JZ)V", (void*) PaintGlue::setDither},
- {"nGetStyle","!(J)I", (void*) PaintGlue::getStyle},
- {"nSetStyle","!(JI)V", (void*) PaintGlue::setStyle},
- {"nGetColor","!(J)I", (void*) PaintGlue::getColor},
- {"nSetColor","!(JI)V", (void*) PaintGlue::setColor},
- {"nGetAlpha","!(J)I", (void*) PaintGlue::getAlpha},
- {"nSetAlpha","!(JI)V", (void*) PaintGlue::setAlpha},
- {"nGetStrokeWidth","!(J)F", (void*) PaintGlue::getStrokeWidth},
- {"nSetStrokeWidth","!(JF)V", (void*) PaintGlue::setStrokeWidth},
- {"nGetStrokeMiter","!(J)F", (void*) PaintGlue::getStrokeMiter},
- {"nSetStrokeMiter","!(JF)V", (void*) PaintGlue::setStrokeMiter},
- {"nGetStrokeCap","!(J)I", (void*) PaintGlue::getStrokeCap},
- {"nSetStrokeCap","!(JI)V", (void*) PaintGlue::setStrokeCap},
- {"nGetStrokeJoin","!(J)I", (void*) PaintGlue::getStrokeJoin},
- {"nSetStrokeJoin","!(JI)V", (void*) PaintGlue::setStrokeJoin},
- {"nGetFillPath","!(JJJ)Z", (void*) PaintGlue::getFillPath},
- {"nSetShader","!(JJ)J", (void*) PaintGlue::setShader},
- {"nSetColorFilter","!(JJ)J", (void*) PaintGlue::setColorFilter},
- {"nSetXfermode","!(JI)V", (void*) PaintGlue::setXfermode},
- {"nSetPathEffect","!(JJ)J", (void*) PaintGlue::setPathEffect},
- {"nSetMaskFilter","!(JJ)J", (void*) PaintGlue::setMaskFilter},
- {"nSetTypeface","!(JJ)J", (void*) PaintGlue::setTypeface},
- {"nSetRasterizer","!(JJ)J", (void*) PaintGlue::setRasterizer},
- {"nGetTextAlign","!(J)I", (void*) PaintGlue::getTextAlign},
- {"nSetTextAlign","!(JI)V", (void*) PaintGlue::setTextAlign},
- {"nSetTextLocales","!(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales},
- {"nSetTextLocalesByMinikinLangListId","!(JI)V",
- (void*) PaintGlue::setTextLocalesByMinikinLangListId},
- {"nIsElegantTextHeight","!(J)Z", (void*) PaintGlue::isElegantTextHeight},
- {"nSetElegantTextHeight","!(JZ)V", (void*) PaintGlue::setElegantTextHeight},
- {"nGetTextSize","!(J)F", (void*) PaintGlue::getTextSize},
- {"nSetTextSize","!(JF)V", (void*) PaintGlue::setTextSize},
- {"nGetTextScaleX","!(J)F", (void*) PaintGlue::getTextScaleX},
- {"nSetTextScaleX","!(JF)V", (void*) PaintGlue::setTextScaleX},
- {"nGetTextSkewX","!(J)F", (void*) PaintGlue::getTextSkewX},
- {"nSetTextSkewX","!(JF)V", (void*) PaintGlue::setTextSkewX},
- {"nGetLetterSpacing","!(J)F", (void*) PaintGlue::getLetterSpacing},
- {"nSetLetterSpacing","!(JF)V", (void*) PaintGlue::setLetterSpacing},
- {"nSetFontFeatureSettings","(JLjava/lang/String;)V",
- (void*) PaintGlue::setFontFeatureSettings},
- {"nGetHyphenEdit", "!(J)I", (void*) PaintGlue::getHyphenEdit},
- {"nSetHyphenEdit", "!(JI)V", (void*) PaintGlue::setHyphenEdit},
- {"nAscent","!(JJ)F", (void*) PaintGlue::ascent},
- {"nDescent","!(JJ)F", (void*) PaintGlue::descent},
-
- {"nGetFontMetrics", "!(JJLandroid/graphics/Paint$FontMetrics;)F",
- (void*)PaintGlue::getFontMetrics},
- {"nGetFontMetricsInt", "!(JJLandroid/graphics/Paint$FontMetricsInt;)I",
- (void*)PaintGlue::getFontMetricsInt},
-
{"nBreakText","(JJ[CIIFI[F)I", (void*) PaintGlue::breakTextC},
{"nBreakText","(JJLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS},
{"nGetTextAdvances","(JJ[CIIIII[FI)F",
@@ -1034,8 +981,74 @@
{"nGetOffsetForAdvance", "(JJ[CIIIIZF)I",
(void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
- {"nSetShadowLayer", "!(JFFFI)V", (void*)PaintGlue::setShadowLayer},
- {"nHasShadowLayer", "!(J)Z", (void*)PaintGlue::hasShadowLayer}
+ // --------------- @FastNative ----------------------
+
+ {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales},
+ {"nSetFontFeatureSettings","(JLjava/lang/String;)V",
+ (void*) PaintGlue::setFontFeatureSettings},
+ {"nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F",
+ (void*)PaintGlue::getFontMetrics},
+ {"nGetFontMetricsInt", "(JJLandroid/graphics/Paint$FontMetricsInt;)I",
+ (void*)PaintGlue::getFontMetricsInt},
+
+ // --------------- @CriticalNative ------------------
+
+ {"nReset","(J)V", (void*) PaintGlue::reset},
+ {"nSet","(JJ)V", (void*) PaintGlue::assign},
+ {"nGetFlags","(J)I", (void*) PaintGlue::getFlags},
+ {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags},
+ {"nGetHinting","(J)I", (void*) PaintGlue::getHinting},
+ {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting},
+ {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias},
+ {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText},
+ {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText},
+ {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText},
+ {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText},
+ {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText},
+ {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap},
+ {"nSetDither","(JZ)V", (void*) PaintGlue::setDither},
+ {"nGetStyle","(J)I", (void*) PaintGlue::getStyle},
+ {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle},
+ {"nGetColor","(J)I", (void*) PaintGlue::getColor},
+ {"nSetColor","(JI)V", (void*) PaintGlue::setColor},
+ {"nGetAlpha","(J)I", (void*) PaintGlue::getAlpha},
+ {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha},
+ {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth},
+ {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth},
+ {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter},
+ {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter},
+ {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap},
+ {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap},
+ {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
+ {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
+ {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
+ {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
+ {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
+ {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
+ {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
+ {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter},
+ {"nSetTypeface","(JJ)J", (void*) PaintGlue::setTypeface},
+ {"nSetRasterizer","(JJ)J", (void*) PaintGlue::setRasterizer},
+ {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign},
+ {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign},
+ {"nSetTextLocalesByMinikinLangListId","(JI)V",
+ (void*) PaintGlue::setTextLocalesByMinikinLangListId},
+ {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight},
+ {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight},
+ {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize},
+ {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize},
+ {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX},
+ {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX},
+ {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX},
+ {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX},
+ {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing},
+ {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing},
+ {"nGetHyphenEdit", "(J)I", (void*) PaintGlue::getHyphenEdit},
+ {"nSetHyphenEdit", "(JI)V", (void*) PaintGlue::setHyphenEdit},
+ {"nAscent","(JJ)F", (void*) PaintGlue::ascent},
+ {"nDescent","(JJ)F", (void*) PaintGlue::descent},
+ {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer},
+ {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}
};
int register_android_graphics_Paint(JNIEnv* env) {
diff --git a/core/jni/android/graphics/PathEffect.cpp b/core/jni/android/graphics/PathEffect.cpp
index b289b21..e801da3 100644
--- a/core/jni/android/graphics/PathEffect.cpp
+++ b/core/jni/android/graphics/PathEffect.cpp
@@ -20,7 +20,8 @@
jlong outerHandle, jlong innerHandle) {
SkPathEffect* outer = reinterpret_cast<SkPathEffect*>(outerHandle);
SkPathEffect* inner = reinterpret_cast<SkPathEffect*>(innerHandle);
- SkPathEffect* effect = SkComposePathEffect::Create(outer, inner);
+ SkPathEffect* effect = SkComposePathEffect::Make(sk_ref_sp(outer),
+ sk_ref_sp(inner)).release();
return reinterpret_cast<jlong>(effect);
}
@@ -28,7 +29,8 @@
jlong firstHandle, jlong secondHandle) {
SkPathEffect* first = reinterpret_cast<SkPathEffect*>(firstHandle);
SkPathEffect* second = reinterpret_cast<SkPathEffect*>(secondHandle);
- SkPathEffect* effect = SkSumPathEffect::Create(first, second);
+ SkPathEffect* effect = SkSumPathEffect::Make(sk_ref_sp(first),
+ sk_ref_sp(second)).release();
return reinterpret_cast<jlong>(effect);
}
@@ -41,7 +43,7 @@
#else
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkPathEffect* effect = SkDashPathEffect::Create(intervals, count, phase);
+ SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release();
return reinterpret_cast<jlong>(effect);
}
@@ -49,19 +51,19 @@
jlong shapeHandle, jfloat advance, jfloat phase, jint style) {
const SkPath* shape = reinterpret_cast<SkPath*>(shapeHandle);
SkASSERT(shape != NULL);
- SkPathEffect* effect = SkPath1DPathEffect::Create(*shape, advance, phase,
- (SkPath1DPathEffect::Style)style);
+ SkPathEffect* effect = SkPath1DPathEffect::Make(*shape, advance, phase,
+ (SkPath1DPathEffect::Style)style).release();
return reinterpret_cast<jlong>(effect);
}
static jlong Corner_constructor(JNIEnv* env, jobject, jfloat radius){
- SkPathEffect* effect = SkCornerPathEffect::Create(radius);
+ SkPathEffect* effect = SkCornerPathEffect::Make(radius).release();
return reinterpret_cast<jlong>(effect);
}
static jlong Discrete_constructor(JNIEnv* env, jobject,
jfloat length, jfloat deviation) {
- SkPathEffect* effect = SkDiscretePathEffect::Create(length, deviation);
+ SkPathEffect* effect = SkDiscretePathEffect::Make(length, deviation).release();
return reinterpret_cast<jlong>(effect);
}
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index 07e14a2..d1ddfab 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -27,9 +27,9 @@
mWidth = src->width();
mHeight = src->height();
if (NULL != src->mPicture.get()) {
- mPicture.reset(SkRef(src->mPicture.get()));
+ mPicture = src->mPicture;
} else if (NULL != src->mRecorder.get()) {
- mPicture.reset(src->makePartialCopy());
+ mPicture = src->makePartialCopy();
}
validate();
} else {
@@ -49,7 +49,7 @@
void Picture::endRecording() {
if (NULL != mRecorder.get()) {
- mPicture.reset(mRecorder->endRecording());
+ mPicture = mRecorder->finishRecordingAsPicture();
validate();
mRecorder.reset(NULL);
}
@@ -68,9 +68,9 @@
Picture* Picture::CreateFromStream(SkStream* stream) {
Picture* newPict = new Picture;
- SkPicture* skPicture = SkPicture::CreateFromStream(stream);
+ sk_sp<SkPicture> skPicture = SkPicture::MakeFromStream(stream);
if (NULL != skPicture) {
- newPict->mPicture.reset(skPicture);
+ newPict->mPicture = skPicture;
const SkIRect cullRect = skPicture->cullRect().roundOut();
newPict->mWidth = cullRect.width();
@@ -82,16 +82,15 @@
void Picture::serialize(SkWStream* stream) const {
if (NULL != mRecorder.get()) {
- std::unique_ptr<SkPicture> tempPict(this->makePartialCopy());
- tempPict->serialize(stream);
+ this->makePartialCopy()->serialize(stream);
} else if (NULL != mPicture.get()) {
validate();
mPicture->serialize(stream);
} else {
+ // serialize "empty" picture
SkPictureRecorder recorder;
recorder.beginRecording(0, 0);
- std::unique_ptr<SkPicture> empty(recorder.endRecording());
- empty->serialize(stream);
+ recorder.finishRecordingAsPicture()->serialize(stream);
}
}
@@ -102,18 +101,18 @@
}
validate();
if (NULL != mPicture.get()) {
- mPicture.get()->playback(canvas->asSkCanvas());
+ mPicture->playback(canvas->asSkCanvas());
}
}
-SkPicture* Picture::makePartialCopy() const {
+sk_sp<SkPicture> Picture::makePartialCopy() const {
SkASSERT(NULL != mRecorder.get());
SkPictureRecorder reRecorder;
SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0);
mRecorder->partialReplay(canvas);
- return reRecorder.endRecording();
+ return reRecorder.finishRecordingAsPicture();
}
void Picture::validate() const {
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index 26a4f6a..b73b375 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -55,12 +55,12 @@
private:
int mWidth;
int mHeight;
- SkAutoTUnref<const SkPicture> mPicture;
+ sk_sp<SkPicture> mPicture;
std::unique_ptr<SkPictureRecorder> mRecorder;
// Make a copy of a picture that is in the midst of being recorded. The
// resulting picture will have balanced saves and restores.
- SkPicture* makePartialCopy() const;
+ sk_sp<SkPicture> makePartialCopy() const;
void validate() const;
};
diff --git a/core/jni/android/graphics/Rasterizer.cpp b/core/jni/android/graphics/Rasterizer.cpp
index 3784f0d..f409498 100644
--- a/core/jni/android/graphics/Rasterizer.cpp
+++ b/core/jni/android/graphics/Rasterizer.cpp
@@ -34,19 +34,19 @@
virtual ~NativeRasterizer() {}
// Can return NULL, or a ref to the skia rasterizer.
- virtual SkRasterizer* refRasterizer() { return NULL; }
+ virtual sk_sp<SkRasterizer> refRasterizer() { return NULL; }
};
class NativeLayerRasterizer : public NativeRasterizer {
public:
SkLayerRasterizer::Builder fBuilder;
- virtual SkRasterizer* refRasterizer() {
- return fBuilder.snapshotRasterizer();
+ virtual sk_sp<SkRasterizer> refRasterizer() {
+ return fBuilder.snapshot();
}
};
-SkRasterizer* GraphicsJNI::refNativeRasterizer(jlong rasterizerHandle) {
+sk_sp<SkRasterizer> GraphicsJNI::refNativeRasterizer(jlong rasterizerHandle) {
NativeRasterizer* nr = reinterpret_cast<NativeRasterizer*>(rasterizerHandle);
return nr ? nr->refRasterizer() : NULL;
}
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 03462a6..a2416df 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -79,9 +79,9 @@
if (proxyMatrix == *matrix) {
return reinterpret_cast<jlong>(currentShader.detach());
}
- return reinterpret_cast<jlong>(baseShader->newWithLocalMatrix(*matrix));
+ return reinterpret_cast<jlong>(baseShader->makeWithLocalMatrix(*matrix).release());
}
- return reinterpret_cast<jlong>(currentShader->newWithLocalMatrix(*matrix));
+ return reinterpret_cast<jlong>(currentShader->makeWithLocalMatrix(*matrix).release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -126,9 +126,9 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::CreateLinear(pts,
+ SkShader* shader = SkGradientShader::MakeLinear(pts,
reinterpret_cast<const SkColor*>(colorValues), pos, count,
- static_cast<SkShader::TileMode>(tileMode));
+ static_cast<SkShader::TileMode>(tileMode)).release();
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
ThrowIAE_IfNull(env, shader);
@@ -147,7 +147,7 @@
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::CreateLinear(pts, colors, NULL, 2, (SkShader::TileMode)tileMode);
+ SkShader* s = SkGradientShader::MakeLinear(pts, colors, NULL, 2, (SkShader::TileMode)tileMode).release();
ThrowIAE_IfNull(env, s);
return reinterpret_cast<jlong>(s);
@@ -170,9 +170,9 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::CreateRadial(center, radius,
+ SkShader* shader = SkGradientShader::MakeRadial(center, radius,
reinterpret_cast<const SkColor*>(colorValues), pos, count,
- static_cast<SkShader::TileMode>(tileMode));
+ static_cast<SkShader::TileMode>(tileMode)).release();
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues),
JNI_ABORT);
@@ -189,8 +189,8 @@
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::CreateRadial(center, radius, colors, NULL, 2,
- (SkShader::TileMode)tileMode);
+ SkShader* s = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2,
+ (SkShader::TileMode)tileMode).release();
ThrowIAE_IfNull(env, s);
return reinterpret_cast<jlong>(s);
}
@@ -209,8 +209,8 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::CreateSweep(x, y,
- reinterpret_cast<const SkColor*>(colors), pos, count);
+ SkShader* shader = SkGradientShader::MakeSweep(x, y,
+ reinterpret_cast<const SkColor*>(colors), pos, count).release();
env->ReleaseIntArrayElements(jcolors, const_cast<jint*>(colors),
JNI_ABORT);
ThrowIAE_IfNull(env, shader);
@@ -222,7 +222,7 @@
SkColor colors[2];
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::CreateSweep(x, y, colors, NULL, 2);
+ SkShader* s = SkGradientShader::MakeSweep(x, y, colors, NULL, 2).release();
ThrowIAE_IfNull(env, s);
return reinterpret_cast<jlong>(s);
}
@@ -234,8 +234,10 @@
{
SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
- SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
- SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, mode);
+ SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
+ SkShader* shader = SkShader::MakeComposeShader(sk_ref_sp(shaderA),
+ sk_ref_sp(shaderB),
+ (SkXfermode::Mode)mode).release();
return reinterpret_cast<jlong>(shader);
}
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index acf9996..d233f7b 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -81,7 +81,7 @@
assert(mCurrentPage != NULL);
assert(mCurrentPage->mPictureRecorder != NULL);
assert(mCurrentPage->mPicture == NULL);
- mCurrentPage->mPicture = mCurrentPage->mPictureRecorder->endRecording();
+ mCurrentPage->mPicture = mCurrentPage->mPictureRecorder->finishRecordingAsPicture().release();
delete mCurrentPage->mPictureRecorder;
mCurrentPage->mPictureRecorder = NULL;
mCurrentPage = NULL;
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_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 6431b94..fd9e714 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -45,6 +45,7 @@
#include "core_jni_helpers.h"
+#include "ScopedUtfChars.h"
#define LOG_TRACE(...)
//#define LOG_TRACE(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
@@ -264,103 +265,108 @@
ALOGD("loadNativeCode_native");
}
- const char* pathStr = env->GetStringUTFChars(path, NULL);
+ ScopedUtfChars pathStr(env, path);
std::unique_ptr<NativeCode> code;
- bool needNativeBridge = false;
+ bool needs_native_bridge = false;
+ std::string error_msg;
- void* handle = OpenNativeLibrary(env, sdkVersion, pathStr, classLoader, libraryPath);
- if (handle == NULL) {
- if (NativeBridgeIsSupported(pathStr)) {
- handle = NativeBridgeLoadLibrary(pathStr, RTLD_LAZY);
- needNativeBridge = true;
- }
+ void* handle = OpenNativeLibrary(env,
+ sdkVersion,
+ pathStr.c_str(),
+ classLoader,
+ libraryPath,
+ &needs_native_bridge,
+ &error_msg);
+
+ if (handle == nullptr) {
+ ALOGW("NativeActivity LoadNativeLibrary(\"%s\") failed: %s",
+ pathStr.c_str(),
+ error_msg.c_str());
+ return 0;
}
- env->ReleaseStringUTFChars(path, pathStr);
- if (handle != NULL) {
- void* funcPtr = NULL;
- const char* funcStr = env->GetStringUTFChars(funcName, NULL);
- if (needNativeBridge) {
- funcPtr = NativeBridgeGetTrampoline(handle, funcStr, NULL, 0);
- } else {
- funcPtr = dlsym(handle, funcStr);
- }
-
- code.reset(new NativeCode(handle, (ANativeActivity_createFunc*)funcPtr));
- env->ReleaseStringUTFChars(funcName, funcStr);
-
- if (code->createActivityFunc == NULL) {
- ALOGW("ANativeActivity_onCreate not found");
- return 0;
- }
-
- code->messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueue);
- if (code->messageQueue == NULL) {
- ALOGW("Unable to retrieve native MessageQueue");
- return 0;
- }
-
- int msgpipe[2];
- if (pipe(msgpipe)) {
- ALOGW("could not create pipe: %s", strerror(errno));
- return 0;
- }
- code->mainWorkRead = msgpipe[0];
- code->mainWorkWrite = msgpipe[1];
- int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK);
- SLOGW_IF(result != 0, "Could not make main work read pipe "
- "non-blocking: %s", strerror(errno));
- result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK);
- SLOGW_IF(result != 0, "Could not make main work write pipe "
- "non-blocking: %s", strerror(errno));
- code->messageQueue->getLooper()->addFd(
- code->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, code.get());
-
- code->ANativeActivity::callbacks = &code->callbacks;
- if (env->GetJavaVM(&code->vm) < 0) {
- ALOGW("NativeActivity GetJavaVM failed");
- return 0;
- }
- code->env = env;
- code->clazz = env->NewGlobalRef(clazz);
-
- const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL);
- code->internalDataPathObj = dirStr;
- code->internalDataPath = code->internalDataPathObj.string();
- env->ReleaseStringUTFChars(internalDataDir, dirStr);
-
- if (externalDataDir != NULL) {
- dirStr = env->GetStringUTFChars(externalDataDir, NULL);
- code->externalDataPathObj = dirStr;
- env->ReleaseStringUTFChars(externalDataDir, dirStr);
- }
- code->externalDataPath = code->externalDataPathObj.string();
-
- code->sdkVersion = sdkVersion;
-
- code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
-
- if (obbDir != NULL) {
- dirStr = env->GetStringUTFChars(obbDir, NULL);
- code->obbPathObj = dirStr;
- env->ReleaseStringUTFChars(obbDir, dirStr);
- }
- code->obbPath = code->obbPathObj.string();
-
- jbyte* rawSavedState = NULL;
- jsize rawSavedSize = 0;
- if (savedState != NULL) {
- rawSavedState = env->GetByteArrayElements(savedState, NULL);
- rawSavedSize = env->GetArrayLength(savedState);
- }
-
- code->createActivityFunc(code.get(), rawSavedState, rawSavedSize);
-
- if (rawSavedState != NULL) {
- env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
- }
+ void* funcPtr = NULL;
+ const char* funcStr = env->GetStringUTFChars(funcName, NULL);
+ if (needs_native_bridge) {
+ funcPtr = NativeBridgeGetTrampoline(handle, funcStr, NULL, 0);
+ } else {
+ funcPtr = dlsym(handle, funcStr);
}
-
+
+ code.reset(new NativeCode(handle, (ANativeActivity_createFunc*)funcPtr));
+ env->ReleaseStringUTFChars(funcName, funcStr);
+
+ if (code->createActivityFunc == NULL) {
+ ALOGW("ANativeActivity_onCreate not found");
+ return 0;
+ }
+
+ code->messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueue);
+ if (code->messageQueue == NULL) {
+ ALOGW("Unable to retrieve native MessageQueue");
+ return 0;
+ }
+
+ int msgpipe[2];
+ if (pipe(msgpipe)) {
+ ALOGW("could not create pipe: %s", strerror(errno));
+ return 0;
+ }
+ code->mainWorkRead = msgpipe[0];
+ code->mainWorkWrite = msgpipe[1];
+ int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK);
+ SLOGW_IF(result != 0, "Could not make main work read pipe "
+ "non-blocking: %s", strerror(errno));
+ result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK);
+ SLOGW_IF(result != 0, "Could not make main work write pipe "
+ "non-blocking: %s", strerror(errno));
+ code->messageQueue->getLooper()->addFd(
+ code->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, code.get());
+
+ code->ANativeActivity::callbacks = &code->callbacks;
+ if (env->GetJavaVM(&code->vm) < 0) {
+ ALOGW("NativeActivity GetJavaVM failed");
+ return 0;
+ }
+ code->env = env;
+ code->clazz = env->NewGlobalRef(clazz);
+
+ const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL);
+ code->internalDataPathObj = dirStr;
+ code->internalDataPath = code->internalDataPathObj.string();
+ env->ReleaseStringUTFChars(internalDataDir, dirStr);
+
+ if (externalDataDir != NULL) {
+ dirStr = env->GetStringUTFChars(externalDataDir, NULL);
+ code->externalDataPathObj = dirStr;
+ env->ReleaseStringUTFChars(externalDataDir, dirStr);
+ }
+ code->externalDataPath = code->externalDataPathObj.string();
+
+ code->sdkVersion = sdkVersion;
+
+ code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
+
+ if (obbDir != NULL) {
+ dirStr = env->GetStringUTFChars(obbDir, NULL);
+ code->obbPathObj = dirStr;
+ env->ReleaseStringUTFChars(obbDir, dirStr);
+ }
+ code->obbPath = code->obbPathObj.string();
+
+ jbyte* rawSavedState = NULL;
+ jsize rawSavedSize = 0;
+ if (savedState != NULL) {
+ rawSavedState = env->GetByteArrayElements(savedState, NULL);
+ rawSavedSize = env->GetArrayLength(savedState);
+ }
+
+ code->createActivityFunc(code.get(), rawSavedState, rawSavedSize);
+
+ if (rawSavedState != NULL) {
+ env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
+ }
+
return (jlong)code.release();
}
diff --git a/core/jni/android_app_admin_SecurityLog.cpp b/core/jni/android_app_admin_SecurityLog.cpp
index da47c4c..e8ca793 100644
--- a/core/jni/android_app_admin_SecurityLog.cpp
+++ b/core/jni/android_app_admin_SecurityLog.cpp
@@ -19,7 +19,7 @@
#include "JNIHelp.h"
#include "core_jni_helpers.h"
#include "jni.h"
-#include "log/logger.h"
+#include <private/android_logger.h>
// The size of the tag number comes out of the payload size.
#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index d5a7a90..04a7543 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -207,7 +207,7 @@
}
static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) {
- SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
+ SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
get_canvas(canvasHandle)->drawColor(color, mode);
}
@@ -341,14 +341,12 @@
jlong paintHandle, jint dstDensity, jint srcDensity) {
Canvas* canvas = get_canvas(canvasHandle);
- Bitmap* bitmap = reinterpret_cast<Bitmap*>(bitmapHandle);
- SkBitmap skiaBitmap;
- bitmap->getSkBitmap(&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);
@@ -362,19 +360,18 @@
}
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();
}
}
-static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jobject jbitmap,
+static void drawBitmap(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap,
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) {
@@ -409,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);
}
@@ -421,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) {
@@ -443,12 +438,12 @@
jboolean hasAlpha, jlong paintHandle) {
// Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will
// correct the alphaType to kOpaque_SkAlphaType.
- SkImageInfo info = SkImageInfo::Make(width, height,
- hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
- kPremul_SkAlphaType);
+ SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType,
+ GraphicsJNI::defaultColorSpace());
SkBitmap bitmap;
bitmap.setInfo(info);
- if (!GraphicsJNI::allocatePixels(env, &bitmap, NULL)) {
+ sk_sp<Bitmap> androidBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL);
+ if (!androidBitmap) {
return;
}
@@ -457,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,
@@ -468,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);
}
@@ -573,65 +567,77 @@
}; // namespace CanvasJNI
static const JNINativeMethod gMethods[] = {
- {"getNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
- {"initRaster", "(Landroid/graphics/Bitmap;)J", (void*) CanvasJNI::initRaster},
- {"native_setBitmap", "!(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
- {"native_isOpaque","!(J)Z", (void*) CanvasJNI::isOpaque},
- {"native_getWidth","!(J)I", (void*) CanvasJNI::getWidth},
- {"native_getHeight","!(J)I", (void*) CanvasJNI::getHeight},
- {"native_setHighContrastText","!(JZ)V", (void*) CanvasJNI::setHighContrastText},
- {"native_save","!(JI)I", (void*) CanvasJNI::save},
- {"native_saveLayer","!(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
- {"native_saveLayerAlpha","!(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
- {"native_getSaveCount","!(J)I", (void*) CanvasJNI::getSaveCount},
- {"native_restore","!(JZ)V", (void*) CanvasJNI::restore},
- {"native_restoreToCount","!(JIZ)V", (void*) CanvasJNI::restoreToCount},
- {"native_getCTM", "!(JJ)V", (void*)CanvasJNI::getCTM},
- {"native_setMatrix","!(JJ)V", (void*) CanvasJNI::setMatrix},
- {"native_concat","!(JJ)V", (void*) CanvasJNI::concat},
- {"native_rotate","!(JF)V", (void*) CanvasJNI::rotate},
- {"native_scale","!(JFF)V", (void*) CanvasJNI::scale},
- {"native_skew","!(JFF)V", (void*) CanvasJNI::skew},
- {"native_translate","!(JFF)V", (void*) CanvasJNI::translate},
- {"native_getClipBounds","!(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
- {"native_quickReject","!(JJ)Z", (void*) CanvasJNI::quickRejectPath},
- {"native_quickReject","!(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
- {"native_clipRect","!(JFFFFI)Z", (void*) CanvasJNI::clipRect},
- {"native_clipPath","!(JJI)Z", (void*) CanvasJNI::clipPath},
- {"native_clipRegion","!(JJI)Z", (void*) CanvasJNI::clipRegion},
- {"native_drawColor","!(JII)V", (void*) CanvasJNI::drawColor},
- {"native_drawPaint","!(JJ)V", (void*) CanvasJNI::drawPaint},
- {"native_drawPoint", "!(JFFJ)V", (void*) CanvasJNI::drawPoint},
- {"native_drawPoints", "!(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
- {"native_drawLine", "!(JFFFFJ)V", (void*) CanvasJNI::drawLine},
- {"native_drawLines", "!(J[FIIJ)V", (void*) CanvasJNI::drawLines},
- {"native_drawRect","!(JFFFFJ)V", (void*) CanvasJNI::drawRect},
- {"native_drawRegion", "!(JJJ)V", (void*) CanvasJNI::drawRegion },
- {"native_drawRoundRect","!(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
- {"native_drawCircle","!(JFFFJ)V", (void*) CanvasJNI::drawCircle},
- {"native_drawOval","!(JFFFFJ)V", (void*) CanvasJNI::drawOval},
- {"native_drawArc","!(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
- {"native_drawPath","!(JJJ)V", (void*) CanvasJNI::drawPath},
- {"nativeDrawVertices", "!(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
- {"native_drawNinePatch", "!(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
- {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFJIII)V", (void*) CanvasJNI::drawBitmap},
- {"nativeDrawBitmapMatrix", "!(JLandroid/graphics/Bitmap;JJ)V", (void*)CanvasJNI::drawBitmapMatrix},
- {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
- {"native_drawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
- {"nativeDrawBitmapMesh", "!(JLandroid/graphics/Bitmap;II[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
- {"native_drawText","!(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
- {"native_drawText","!(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
- {"native_drawTextRun","!(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
- {"native_drawTextRun","!(JLjava/lang/String;IIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunString},
- {"native_drawTextOnPath","!(J[CIIJFFIJJ)V", (void*) CanvasJNI::drawTextOnPathChars},
- {"native_drawTextOnPath","!(JLjava/lang/String;JFFIJJ)V", (void*) CanvasJNI::drawTextOnPathString},
- {"nativeSetDrawFilter", "!(JJ)V", (void*) CanvasJNI::setDrawFilter},
- {"freeCaches", "()V", (void*) CanvasJNI::freeCaches},
- {"freeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches}
+ {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
+ {"nInitRaster", "(Landroid/graphics/Bitmap;)J", (void*) CanvasJNI::initRaster},
+ {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
+ {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
+
+ // ------------ @FastNative ----------------
+ {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
+ {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
+ {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
+ {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
+ {"nSetHighContrastText","(JZ)V", (void*) CanvasJNI::setHighContrastText},
+ {"nSave","(JI)I", (void*) CanvasJNI::save},
+ {"nSaveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
+ {"nSaveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
+ {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
+ {"nRestore","(JZ)V", (void*) CanvasJNI::restore},
+ {"nRestoreToCount","(JIZ)V", (void*) CanvasJNI::restoreToCount},
+ {"nGetCTM", "(JJ)V", (void*)CanvasJNI::getCTM},
+ {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
+ {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
+ {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
+ {"nScale","(JFF)V", (void*) CanvasJNI::scale},
+ {"nSkew","(JFF)V", (void*) CanvasJNI::skew},
+ {"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
+ {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+ {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
+ {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+ {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
+ {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
+ {"nClipRegion","(JJI)Z", (void*) CanvasJNI::clipRegion},
+ {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+};
+
+// If called from Canvas these are regular JNI
+// If called from DisplayListCanvas they are @FastNative
+static const JNINativeMethod gDrawMethods[] = {
+ {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
+ {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
+ {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
+ {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
+ {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
+ {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
+ {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
+ {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion },
+ {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
+ {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
+ {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
+ {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
+ {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
+ {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+ {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
+ {"nDrawBitmapMatrix", "(JLandroid/graphics/Bitmap;JJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+ {"nDrawBitmapMesh", "(JLandroid/graphics/Bitmap;II[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+ {"nDrawBitmap","(JLandroid/graphics/Bitmap;FFJIII)V", (void*) CanvasJNI::drawBitmap},
+ {"nDrawBitmap","(JLandroid/graphics/Bitmap;FFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
+ {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+ {"nDrawText","(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
+ {"nDrawText","(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
+ {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
+ {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunString},
+ {"nDrawTextOnPath","(J[CIIJFFIJJ)V", (void*) CanvasJNI::drawTextOnPathChars},
+ {"nDrawTextOnPath","(JLjava/lang/String;JFFIJJ)V", (void*) CanvasJNI::drawTextOnPathString},
};
int register_android_graphics_Canvas(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "android/graphics/Canvas", gMethods, NELEM(gMethods));
+ int ret = 0;
+ ret |= RegisterMethodsOrDie(env, "android/graphics/Canvas", gMethods, NELEM(gMethods));
+ ret |= RegisterMethodsOrDie(env, "android/graphics/BaseCanvas", gDrawMethods, NELEM(gDrawMethods));
+ ret |= RegisterMethodsOrDie(env, "android/view/RecordingCanvas", gDrawMethods, NELEM(gDrawMethods));
+ return ret;
+
}
}; // namespace android
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index ade718b..99a5fc7 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -188,17 +188,20 @@
{"nCreateAnimatorSet", "()J", (void*)createAnimatorSet},
{"nSetVectorDrawableTarget", "(JJ)V", (void*)setVectorDrawableTarget},
{"nAddAnimator", "(JJJJJII)V", (void*)addAnimator},
- {"nCreateGroupPropertyHolder", "!(JIFF)J", (void*)createGroupPropertyHolder},
- {"nCreatePathDataPropertyHolder", "!(JJJ)J", (void*)createPathDataPropertyHolder},
- {"nCreatePathColorPropertyHolder", "!(JIII)J", (void*)createPathColorPropertyHolder},
- {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
- {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
{"nSetPropertyHolderData", "(J[FI)V", (void*)setFloatPropertyHolderData},
{"nSetPropertyHolderData", "(J[II)V", (void*)setIntPropertyHolderData},
{"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V", (void*)start},
{"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT;I)V", (void*)reverse},
- {"nEnd", "!(J)V", (void*)end},
- {"nReset", "!(J)V", (void*)reset},
+
+ // ------------- @FastNative -------------------
+
+ {"nCreateGroupPropertyHolder", "(JIFF)J", (void*)createGroupPropertyHolder},
+ {"nCreatePathDataPropertyHolder", "(JJJ)J", (void*)createPathDataPropertyHolder},
+ {"nCreatePathColorPropertyHolder", "(JIII)J", (void*)createPathColorPropertyHolder},
+ {"nCreatePathPropertyHolder", "(JIFF)J", (void*)createPathPropertyHolder},
+ {"nCreateRootAlphaPropertyHolder", "(JFF)J", (void*)createRootAlphaPropertyHolder},
+ {"nEnd", "(J)V", (void*)end},
+ {"nReset", "(J)V", (void*)reset},
};
const char* const kClassPathName = "android/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorRT";
diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp
index 045f127..e9ea702 100644
--- a/core/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -350,64 +350,66 @@
}
static const JNINativeMethod gMethods[] = {
- {"nCreateTree", "!(J)J", (void*)createTree},
- {"nCreateTreeFromCopy", "!(JJ)J", (void*)createTreeFromCopy},
- {"nSetRendererViewportSize", "!(JFF)V", (void*)setTreeViewportSize},
- {"nSetRootAlpha", "!(JF)Z", (void*)setRootAlpha},
- {"nGetRootAlpha", "!(J)F", (void*)getRootAlpha},
- {"nSetAllowCaching", "!(JZ)V", (void*)setAllowCaching},
-
{"nDraw", "(JJJLandroid/graphics/Rect;ZZ)I", (void*)draw},
- {"nCreateFullPath", "!()J", (void*)createEmptyFullPath},
- {"nCreateFullPath", "!(J)J", (void*)createFullPath},
- {"nUpdateFullPathProperties", "!(JFIFIFFFFFIII)V", (void*)updateFullPathPropertiesAndStrokeStyles},
- {"nUpdateFullPathFillGradient", "!(JJ)V", (void*)updateFullPathFillGradient},
- {"nUpdateFullPathStrokeGradient", "!(JJ)V", (void*)updateFullPathStrokeGradient},
{"nGetFullPathProperties", "(J[BI)Z", (void*)getFullPathProperties},
{"nGetGroupProperties", "(J[FI)Z", (void*)getGroupProperties},
-
- {"nCreateClipPath", "!()J", (void*)createEmptyClipPath},
- {"nCreateClipPath", "!(J)J", (void*)createClipPath},
- {"nCreateGroup", "!()J", (void*)createEmptyGroup},
- {"nCreateGroup", "!(J)J", (void*)createGroup},
- {"nSetName", "(JLjava/lang/String;)V", (void*)setNodeName},
- {"nUpdateGroupProperties", "!(JFFFFFFF)V", (void*)updateGroupProperties},
-
- {"nAddChild", "!(JJ)V", (void*)addChild},
{"nSetPathString", "(JLjava/lang/String;I)V", (void*)setPathString},
+ {"nSetName", "(JLjava/lang/String;)V", (void*)setNodeName},
- {"nGetRotation", "!(J)F", (void*)getRotation},
- {"nSetRotation", "!(JF)V", (void*)setRotation},
- {"nGetPivotX", "!(J)F", (void*)getPivotX},
- {"nSetPivotX", "!(JF)V", (void*)setPivotX},
- {"nGetPivotY", "!(J)F", (void*)getPivotY},
- {"nSetPivotY", "!(JF)V", (void*)setPivotY},
- {"nGetScaleX", "!(J)F", (void*)getScaleX},
- {"nSetScaleX", "!(JF)V", (void*)setScaleX},
- {"nGetScaleY", "!(J)F", (void*)getScaleY},
- {"nSetScaleY", "!(JF)V", (void*)setScaleY},
- {"nGetTranslateX", "!(J)F", (void*)getTranslateX},
- {"nSetTranslateX", "!(JF)V", (void*)setTranslateX},
- {"nGetTranslateY", "!(J)F", (void*)getTranslateY},
- {"nSetTranslateY", "!(JF)V", (void*)setTranslateY},
+ // ------------- @FastNative ----------------
- {"nSetPathData", "!(JJ)V", (void*)setPathData},
- {"nGetStrokeWidth", "!(J)F", (void*)getStrokeWidth},
- {"nSetStrokeWidth", "!(JF)V", (void*)setStrokeWidth},
- {"nGetStrokeColor", "!(J)I", (void*)getStrokeColor},
- {"nSetStrokeColor", "!(JI)V", (void*)setStrokeColor},
- {"nGetStrokeAlpha", "!(J)F", (void*)getStrokeAlpha},
- {"nSetStrokeAlpha", "!(JF)V", (void*)setStrokeAlpha},
- {"nGetFillColor", "!(J)I", (void*)getFillColor},
- {"nSetFillColor", "!(JI)V", (void*)setFillColor},
- {"nGetFillAlpha", "!(J)F", (void*)getFillAlpha},
- {"nSetFillAlpha", "!(JF)V", (void*)setFillAlpha},
- {"nGetTrimPathStart", "!(J)F", (void*)getTrimPathStart},
- {"nSetTrimPathStart", "!(JF)V", (void*)setTrimPathStart},
- {"nGetTrimPathEnd", "!(J)F", (void*)getTrimPathEnd},
- {"nSetTrimPathEnd", "!(JF)V", (void*)setTrimPathEnd},
- {"nGetTrimPathOffset", "!(J)F", (void*)getTrimPathOffset},
- {"nSetTrimPathOffset", "!(JF)V", (void*)setTrimPathOffset},
+ {"nCreateTree", "(J)J", (void*)createTree},
+ {"nCreateTreeFromCopy", "(JJ)J", (void*)createTreeFromCopy},
+ {"nSetRendererViewportSize", "(JFF)V", (void*)setTreeViewportSize},
+ {"nSetRootAlpha", "(JF)Z", (void*)setRootAlpha},
+ {"nGetRootAlpha", "(J)F", (void*)getRootAlpha},
+ {"nSetAllowCaching", "(JZ)V", (void*)setAllowCaching},
+
+ {"nCreateFullPath", "()J", (void*)createEmptyFullPath},
+ {"nCreateFullPath", "(J)J", (void*)createFullPath},
+ {"nUpdateFullPathProperties", "(JFIFIFFFFFIII)V", (void*)updateFullPathPropertiesAndStrokeStyles},
+ {"nUpdateFullPathFillGradient", "(JJ)V", (void*)updateFullPathFillGradient},
+ {"nUpdateFullPathStrokeGradient", "(JJ)V", (void*)updateFullPathStrokeGradient},
+
+ {"nCreateClipPath", "()J", (void*)createEmptyClipPath},
+ {"nCreateClipPath", "(J)J", (void*)createClipPath},
+ {"nCreateGroup", "()J", (void*)createEmptyGroup},
+ {"nCreateGroup", "(J)J", (void*)createGroup},
+ {"nUpdateGroupProperties", "(JFFFFFFF)V", (void*)updateGroupProperties},
+
+ {"nAddChild", "(JJ)V", (void*)addChild},
+ {"nGetRotation", "(J)F", (void*)getRotation},
+ {"nSetRotation", "(JF)V", (void*)setRotation},
+ {"nGetPivotX", "(J)F", (void*)getPivotX},
+ {"nSetPivotX", "(JF)V", (void*)setPivotX},
+ {"nGetPivotY", "(J)F", (void*)getPivotY},
+ {"nSetPivotY", "(JF)V", (void*)setPivotY},
+ {"nGetScaleX", "(J)F", (void*)getScaleX},
+ {"nSetScaleX", "(JF)V", (void*)setScaleX},
+ {"nGetScaleY", "(J)F", (void*)getScaleY},
+ {"nSetScaleY", "(JF)V", (void*)setScaleY},
+ {"nGetTranslateX", "(J)F", (void*)getTranslateX},
+ {"nSetTranslateX", "(JF)V", (void*)setTranslateX},
+ {"nGetTranslateY", "(J)F", (void*)getTranslateY},
+ {"nSetTranslateY", "(JF)V", (void*)setTranslateY},
+
+ {"nSetPathData", "(JJ)V", (void*)setPathData},
+ {"nGetStrokeWidth", "(J)F", (void*)getStrokeWidth},
+ {"nSetStrokeWidth", "(JF)V", (void*)setStrokeWidth},
+ {"nGetStrokeColor", "(J)I", (void*)getStrokeColor},
+ {"nSetStrokeColor", "(JI)V", (void*)setStrokeColor},
+ {"nGetStrokeAlpha", "(J)F", (void*)getStrokeAlpha},
+ {"nSetStrokeAlpha", "(JF)V", (void*)setStrokeAlpha},
+ {"nGetFillColor", "(J)I", (void*)getFillColor},
+ {"nSetFillColor", "(JI)V", (void*)setFillColor},
+ {"nGetFillAlpha", "(J)F", (void*)getFillAlpha},
+ {"nSetFillAlpha", "(JF)V", (void*)setFillAlpha},
+ {"nGetTrimPathStart", "(J)F", (void*)getTrimPathStart},
+ {"nSetTrimPathStart", "(JF)V", (void*)setTrimPathStart},
+ {"nGetTrimPathEnd", "(J)F", (void*)getTrimPathEnd},
+ {"nSetTrimPathEnd", "(JF)V", (void*)setTrimPathEnd},
+ {"nGetTrimPathOffset", "(J)F", (void*)getTrimPathOffset},
+ {"nSetTrimPathOffset", "(JF)V", (void*)setTrimPathOffset},
};
int register_android_graphics_drawable_VectorDrawable(JNIEnv* env) {
diff --git a/core/jni/android_hardware_Radio.cpp b/core/jni/android_hardware_Radio.cpp
index ec6471e..42ceec4 100644
--- a/core/jni/android_hardware_Radio.cpp
+++ b/core/jni/android_hardware_Radio.cpp
@@ -245,16 +245,17 @@
radio_metadata_key_t key;
radio_metadata_type_t type;
void *value;
- unsigned int size;
+ size_t size;
if (radio_metadata_get_at_index(nMetadata, i , &key, &type, &value, &size) != 0) {
continue;
}
switch (type) {
case RADIO_METADATA_TYPE_INT: {
ALOGV("%s RADIO_METADATA_TYPE_INT %d", __FUNCTION__, key);
+ int32_t val = *(int32_t *)value;
jStatus = env->CallIntMethod(*jMetadata,
gRadioMetadataMethods.putIntFromNative,
- key, *(jint *)value);
+ key, (jint)val);
if (jStatus == 0) {
jCount++;
}
@@ -271,7 +272,7 @@
env->DeleteLocalRef(jText);
} break;
case RADIO_METADATA_TYPE_RAW: {
- ALOGV("%s RADIO_METADATA_TYPE_RAW %d size %u", __FUNCTION__, key, size);
+ ALOGV("%s RADIO_METADATA_TYPE_RAW %d size %zu", __FUNCTION__, key, size);
if (size == 0) {
break;
}
@@ -720,7 +721,7 @@
if (module == NULL) {
return RADIO_STATUS_NO_INIT;
}
- status_t status = module->tune((unsigned int)channel, (unsigned int)subChannel);
+ status_t status = module->tune((uint32_t)channel, (uint32_t)subChannel);
return (jint)status;
}
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index 7ec17bf..6814506 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -24,12 +24,17 @@
#include <usbhost/usbhost.h>
+#include <chrono>
+
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace android;
+using namespace std::chrono;
+
+static const int USB_CONTROL_READ_TIMEOUT_MS = 200;
static jfieldID field_context;
@@ -215,7 +220,7 @@
}
static jobject
-android_hardware_UsbDeviceConnection_request_wait(JNIEnv *env, jobject thiz)
+android_hardware_UsbDeviceConnection_request_wait(JNIEnv *env, jobject thiz, jint timeoutMillis)
{
struct usb_device* device = get_device_from_object(env, thiz);
if (!device) {
@@ -223,11 +228,33 @@
return NULL;
}
- struct usb_request* request = usb_request_wait(device);
- if (request)
+ struct usb_request* request;
+ if (timeoutMillis == -1) {
+ request = usb_request_wait(device, -1);
+ } else {
+ steady_clock::time_point currentTime = steady_clock::now();
+ steady_clock::time_point endTime = currentTime + std::chrono::milliseconds(timeoutMillis);
+
+ // Poll the existence of a request via usb_request_wait until we get a result, an unexpected
+ // error or time out. As several threads can listen on the same fd, we might get wakeups
+ // without data.
+ while (1) {
+ request = usb_request_wait(device, duration_cast<std::chrono::milliseconds>(endTime
+ - currentTime).count());
+
+ int error = errno;
+ currentTime = steady_clock::now();
+ if (request != NULL || error != EAGAIN || currentTime >= endTime) {
+ break;
+ }
+ };
+ }
+
+ if (request) {
return (jobject)request->client_data;
- else
+ } else {
return NULL;
+ }
}
static jstring
@@ -238,7 +265,8 @@
ALOGE("device is closed in native_get_serial");
return NULL;
}
- char* serial = usb_device_get_serial(device);
+ char* serial = usb_device_get_serial(device,
+ USB_CONTROL_READ_TIMEOUT_MS);
if (!serial)
return NULL;
jstring result = env->NewStringUTF(serial);
@@ -272,7 +300,7 @@
(void *)android_hardware_UsbDeviceConnection_control_request},
{"native_bulk_request", "(I[BIII)I",
(void *)android_hardware_UsbDeviceConnection_bulk_request},
- {"native_request_wait", "()Landroid/hardware/usb/UsbRequest;",
+ {"native_request_wait", "(I)Landroid/hardware/usb/UsbRequest;",
(void *)android_hardware_UsbDeviceConnection_request_wait},
{ "native_get_serial", "()Ljava/lang/String;",
(void*)android_hardware_UsbDeviceConnection_get_serial },
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index 8eb39e1..fbccfd5 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -33,6 +33,7 @@
#include <unordered_map>
#include <queue>
+#include <android-base/macros.h>
#include <cutils/log.h>
#include "JNIHelp.h"
@@ -704,10 +705,10 @@
}
jbyteArray jmsg = env->NewByteArray(msgLen);
- jintArray jheader = env->NewIntArray(sizeof(header));
+ jintArray jheader = env->NewIntArray(arraysize(header));
env->SetByteArrayRegion(jmsg, 0, msgLen, (jbyte *)msg);
- env->SetIntArrayRegion(jheader, 0, sizeof(header), (jint *)header);
+ env->SetIntArrayRegion(jheader, 0, arraysize(header), (jint *)header);
ALOGI("Passing msg type %" PRIu32 " from app %" PRIu32 " from hub %" PRIu32,
header[HEADER_FIELD_MSG_TYPE], header[HEADER_FIELD_APP_INSTANCE],
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e0bfecb..aa4570f 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -15,15 +15,7 @@
*/
#define LOG_TAG "android.os.Debug"
-#include "JNIHelp.h"
-#include "jni.h"
-#include <utils/String8.h>
-#include "utils/misc.h"
-#include "cutils/debugger.h"
-#include <memtrack/memtrack.h>
-#include <memunreachable/memunreachable.h>
-#include <cutils/log.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
@@ -40,9 +32,26 @@
#include <iomanip>
#include <string>
+#include "jni.h"
+
+#include "android-base/stringprintf.h"
+#include "cutils/debugger.h"
+#include "cutils/log.h"
+#include "JNIHelp.h"
+#include "memtrack/memtrack.h"
+#include "memunreachable/memunreachable.h"
+#include "utils/misc.h"
+#include "utils/String8.h"
+
namespace android
{
+using UniqueFile = std::unique_ptr<FILE, decltype(&fclose)>;
+
+static inline UniqueFile MakeUniqueFile(const char* path, const char* mode) {
+ return UniqueFile(fopen(path, mode), fclose);
+}
+
enum {
HEAP_UNKNOWN,
HEAP_DALVIK,
@@ -116,8 +125,6 @@
jfieldID otherStats_field;
jfieldID hasSwappedOutPss_field;
-static bool memtrackLoaded;
-
struct stats_t {
int pss;
int swappablePss;
@@ -199,10 +206,6 @@
*/
static int read_memtrack_memory(int pid, struct graphics_memory_pss* graphics_mem)
{
- if (!memtrackLoaded) {
- return -1;
- }
-
struct memtrack_proc* p = memtrack_proc_new();
if (p == NULL) {
ALOGW("failed to create memtrack_proc");
@@ -288,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) {
@@ -427,15 +431,13 @@
static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
{
- char tmp[128];
- FILE *fp;
+ *foundSwapPss = false;
- sprintf(tmp, "/proc/%d/smaps", pid);
- fp = fopen(tmp, "r");
- if (fp == 0) return;
+ std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
+ UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
+ if (fp == nullptr) return;
- read_mapinfo(fp, stats, foundSwapPss);
- fclose(fp);
+ read_mapinfo(fp.get(), stats, foundSwapPss);
}
static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
@@ -517,51 +519,48 @@
jlong uss = 0;
jlong memtrack = 0;
- char tmp[128];
- FILE *fp;
-
struct graphics_memory_pss graphics_mem;
if (read_memtrack_memory(pid, &graphics_mem) == 0) {
pss = uss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;
}
- sprintf(tmp, "/proc/%d/smaps", pid);
- fp = fopen(tmp, "r");
+ {
+ std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
+ UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
- if (fp != 0) {
- while (true) {
- if (fgets(line, 1024, fp) == NULL) {
- break;
- }
+ if (fp != nullptr) {
+ while (true) {
+ if (fgets(line, 1024, fp.get()) == NULL) {
+ break;
+ }
- if (line[0] == 'P') {
- if (strncmp(line, "Pss:", 4) == 0) {
- char* c = line + 4;
+ if (line[0] == 'P') {
+ if (strncmp(line, "Pss:", 4) == 0) {
+ char* c = line + 4;
+ while (*c != 0 && (*c < '0' || *c > '9')) {
+ c++;
+ }
+ pss += atoi(c);
+ } else if (strncmp(line, "Private_Clean:", 14) == 0
+ || strncmp(line, "Private_Dirty:", 14) == 0) {
+ char* c = line + 14;
+ while (*c != 0 && (*c < '0' || *c > '9')) {
+ c++;
+ }
+ uss += atoi(c);
+ }
+ } else if (line[0] == 'S' && strncmp(line, "SwapPss:", 8) == 0) {
+ char* c = line + 8;
+ jlong lSwapPss;
while (*c != 0 && (*c < '0' || *c > '9')) {
c++;
}
- pss += atoi(c);
- } else if (strncmp(line, "Private_Clean:", 14) == 0
- || strncmp(line, "Private_Dirty:", 14) == 0) {
- char* c = line + 14;
- while (*c != 0 && (*c < '0' || *c > '9')) {
- c++;
- }
- uss += atoi(c);
+ lSwapPss = atoi(c);
+ swapPss += lSwapPss;
+ pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP
}
- } else if (line[0] == 'S' && strncmp(line, "SwapPss:", 8) == 0) {
- char* c = line + 8;
- jlong lSwapPss;
- while (*c != 0 && (*c < '0' || *c > '9')) {
- c++;
- }
- lSwapPss = atoi(c);
- swapPss += lSwapPss;
- pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP
}
}
-
- fclose(fp);
}
if (outUssSwapPss != NULL) {
@@ -605,12 +604,14 @@
NULL
};
long size, vmalloc_allocated_size = 0;
- FILE* fp = fopen("/proc/vmallocinfo", "r");
- if (fp == NULL) {
+
+ UniqueFile fp = MakeUniqueFile("/proc/vmallocinfo", "re");
+ if (fp == nullptr) {
return 0;
}
+
while (true) {
- if (fgets(line, 1024, fp) == NULL) {
+ if (fgets(line, 1024, fp.get()) == NULL) {
break;
}
bool valid_line = true;
@@ -626,7 +627,6 @@
vmalloc_allocated_size += size;
}
}
- fclose(fp);
return vmalloc_allocated_size;
}
@@ -650,27 +650,25 @@
static long long get_zram_mem_used()
{
#define ZRAM_SYSFS "/sys/block/zram0/"
- FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
- if (f) {
+ UniqueFile mm_stat_file = MakeUniqueFile(ZRAM_SYSFS "mm_stat", "re");
+ if (mm_stat_file) {
long long mem_used_total = 0;
- int matched = fscanf(f, "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total);
+ int matched = fscanf(mm_stat_file.get(), "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total);
if (matched != 1)
ALOGW("failed to parse " ZRAM_SYSFS "mm_stat");
- fclose(f);
return mem_used_total;
}
- f = fopen(ZRAM_SYSFS "mem_used_total", "r");
- if (f) {
+ UniqueFile mem_used_total_file = MakeUniqueFile(ZRAM_SYSFS "mem_used_total", "re");
+ if (mem_used_total_file) {
long long mem_used_total = 0;
- int matched = fscanf(f, "%lld", &mem_used_total);
+ int matched = fscanf(mem_used_total_file.get(), "%lld", &mem_used_total);
if (matched != 1)
ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total");
- fclose(f);
return mem_used_total;
}
@@ -783,8 +781,8 @@
static jint read_binder_stat(const char* stat)
{
- FILE* fp = fopen(BINDER_STATS, "r");
- if (fp == NULL) {
+ UniqueFile fp = MakeUniqueFile(BINDER_STATS, "re");
+ if (fp == nullptr) {
return -1;
}
@@ -795,8 +793,7 @@
// loop until we have the block that represents this process
do {
- if (fgets(line, 1024, fp) == 0) {
- fclose(fp);
+ if (fgets(line, 1024, fp.get()) == 0) {
return -1;
}
} while (strncmp(compare, line, len));
@@ -805,8 +802,7 @@
len = snprintf(compare, 128, " %s: ", stat);
do {
- if (fgets(line, 1024, fp) == 0) {
- fclose(fp);
+ if (fgets(line, 1024, fp.get()) == 0) {
return -1;
}
} while (strncmp(compare, line, len));
@@ -814,7 +810,6 @@
// we have the line, now increment the line ptr to the value
char* ptr = line + len;
jint result = atoi(ptr);
- fclose(fp);
return result;
}
@@ -839,7 +834,8 @@
size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
extern "C" void free_malloc_leak_info(uint8_t* info);
#define SIZE_FLAG_ZYGOTE_CHILD (1<<31)
-#define BACKTRACE_SIZE 32
+
+static size_t gNumBacktraceElements;
/*
* This is a qsort() callback.
@@ -859,11 +855,11 @@
return -1;
}
- intptr_t* bt1 = (intptr_t*)(rec1 + 2);
- intptr_t* bt2 = (intptr_t*)(rec2 + 2);
- for (size_t idx = 0; idx < BACKTRACE_SIZE; idx++) {
- intptr_t addr1 = bt1[idx];
- intptr_t addr2 = bt2[idx];
+ uintptr_t* bt1 = (uintptr_t*)(rec1 + 2);
+ uintptr_t* bt2 = (uintptr_t*)(rec2 + 2);
+ for (size_t idx = 0; idx < gNumBacktraceElements; idx++) {
+ uintptr_t addr1 = bt1[idx];
+ uintptr_t addr2 = bt2[idx];
if (addr1 == addr2) {
if (addr1 == 0)
break;
@@ -907,9 +903,10 @@
if (info == NULL) {
fprintf(fp, "Native heap dump not available. To enable, run these"
" commands (requires root):\n");
- fprintf(fp, "$ adb shell setprop libc.debug.malloc 1\n");
- fprintf(fp, "$ adb shell stop\n");
- fprintf(fp, "$ adb shell start\n");
+ fprintf(fp, "# adb shell stop\n");
+ fprintf(fp, "# adb shell setprop libc.debug.malloc.options "
+ "backtrace\n");
+ fprintf(fp, "# adb shell start\n");
return;
}
assert(infoSize != 0);
@@ -920,13 +917,11 @@
size_t recordCount = overallSize / infoSize;
fprintf(fp, "Total memory: %zu\n", totalMemory);
fprintf(fp, "Allocation records: %zd\n", recordCount);
- if (backtraceSize != BACKTRACE_SIZE) {
- fprintf(fp, "WARNING: mismatched backtrace sizes (%zu vs. %d)\n",
- backtraceSize, BACKTRACE_SIZE);
- }
+ fprintf(fp, "Backtrace size: %zd\n", backtraceSize);
fprintf(fp, "\n");
/* re-sort the entries */
+ gNumBacktraceElements = backtraceSize;
qsort(info, recordCount, infoSize, compareHeapRecords);
/* dump the entries to the file */
@@ -934,7 +929,7 @@
for (size_t idx = 0; idx < recordCount; idx++) {
size_t size = *(size_t*) ptr;
size_t allocations = *(size_t*) (ptr + sizeof(size_t));
- intptr_t* backtrace = (intptr_t*) (ptr + sizeof(size_t) * 2);
+ uintptr_t* backtrace = (uintptr_t*) (ptr + sizeof(size_t) * 2);
fprintf(fp, "z %d sz %8zu num %4zu bt",
(size & SIZE_FLAG_ZYGOTE_CHILD) != 0,
@@ -960,16 +955,15 @@
fprintf(fp, "MAPS\n");
const char* maps = "/proc/self/maps";
- FILE* in = fopen(maps, "r");
- if (in == NULL) {
+ UniqueFile in = MakeUniqueFile(maps, "re");
+ if (in == nullptr) {
fprintf(fp, "Could not open %s\n", maps);
return;
}
char buf[BUFSIZ];
- while (size_t n = fread(buf, sizeof(char), BUFSIZ, in)) {
+ while (size_t n = fread(buf, sizeof(char), BUFSIZ, in.get())) {
fwrite(buf, sizeof(char), n, fp);
}
- fclose(in);
fprintf(fp, "END\n");
}
@@ -999,8 +993,8 @@
return;
}
- FILE* fp = fdopen(fd, "w");
- if (fp == NULL) {
+ UniqueFile fp(fdopen(fd, "w"), fclose);
+ if (fp == nullptr) {
ALOGW("fdopen(%d) failed: %s\n", fd, strerror(errno));
close(fd);
jniThrowRuntimeException(env, "fdopen() failed");
@@ -1008,10 +1002,8 @@
}
ALOGD("Native heap dump starting...\n");
- dumpNativeHeap(fp);
+ dumpNativeHeap(fp.get());
ALOGD("Native heap dump complete.\n");
-
- fclose(fp);
}
@@ -1093,14 +1085,6 @@
int register_android_os_Debug(JNIEnv *env)
{
- int err = memtrack_init();
- if (err != 0) {
- memtrackLoaded = false;
- ALOGE("failed to load memtrack module: %d", err);
- } else {
- memtrackLoaded = true;
- }
-
jclass clazz = env->FindClass("android/os/Debug$MemoryInfo");
// Sanity check the number of other statistics expected in Java matches here.
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 7da0314..816d5df 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -196,19 +196,32 @@
}
static void JHwBinder_native_registerService(
- JNIEnv *env, jobject thiz, jstring serviceNameObj) {
+ JNIEnv *env,
+ jobject thiz,
+ jstring serviceNameObj,
+ jint versionMajor,
+ jint versionMinor) {
if (serviceNameObj == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
return;
}
+ if (versionMajor < 0
+ || versionMajor > 65535
+ || versionMinor < 0
+ || versionMinor > 65535) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
const jchar *serviceName = env->GetStringCritical(serviceNameObj, NULL);
if (serviceName == NULL) {
return; // XXX exception already pending?
}
- const hardware::hidl_version kVersion = hardware::make_hidl_version(1, 0);
+ const hardware::hidl_version kVersion =
+ hardware::make_hidl_version(versionMajor, versionMinor);
sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz);
@@ -231,19 +244,32 @@
}
static jobject JHwBinder_native_getService(
- JNIEnv *env, jclass /* clazzObj */, jstring serviceNameObj) {
+ JNIEnv *env,
+ jclass /* clazzObj */,
+ jstring serviceNameObj,
+ jint versionMajor,
+ jint versionMinor) {
if (serviceNameObj == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
return NULL;
}
+ if (versionMajor < 0
+ || versionMajor > 65535
+ || versionMinor < 0
+ || versionMinor > 65535) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
+
const jchar *serviceName = env->GetStringCritical(serviceNameObj, NULL);
if (serviceName == NULL) {
return NULL; // XXX exception already pending?
}
- const hardware::hidl_version kVersion = hardware::make_hidl_version(1, 0);
+ const hardware::hidl_version kVersion =
+ hardware::make_hidl_version(versionMajor, versionMinor);
LOG(INFO) << "looking for service '"
<< String8(String16(
@@ -266,6 +292,9 @@
return NULL;
}
+ LOG(INFO) << "Starting thread pool.";
+ ::android::hardware::ProcessState::self()->startThreadPool();
+
return JHwRemoteBinder::NewObject(env, service);
}
@@ -277,10 +306,10 @@
"(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V",
(void *)JHwBinder_native_transact },
- { "registerService", "(Ljava/lang/String;)V",
+ { "registerService", "(Ljava/lang/String;II)V",
(void *)JHwBinder_native_registerService },
- { "getService", "(Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;",
+ { "getService", "(Ljava/lang/String;II)L" PACKAGE_PATH "/IHwBinder;",
(void *)JHwBinder_native_getService },
};
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_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 0a8ae2b..8f7908a 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -722,33 +722,50 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gParcelMethods[] = {
- {"nativeDataSize", "!(J)I", (void*)android_os_Parcel_dataSize},
- {"nativeDataAvail", "!(J)I", (void*)android_os_Parcel_dataAvail},
- {"nativeDataPosition", "!(J)I", (void*)android_os_Parcel_dataPosition},
- {"nativeDataCapacity", "!(J)I", (void*)android_os_Parcel_dataCapacity},
- {"nativeSetDataSize", "!(JI)J", (void*)android_os_Parcel_setDataSize},
- {"nativeSetDataPosition", "!(JI)V", (void*)android_os_Parcel_setDataPosition},
- {"nativeSetDataCapacity", "!(JI)V", (void*)android_os_Parcel_setDataCapacity},
+ // @FastNative
+ {"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
+ // @FastNative
+ {"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
+ // @FastNative
+ {"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
+ // @FastNative
+ {"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
+ // @FastNative
+ {"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
+ // @FastNative
+ {"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
+ // @FastNative
+ {"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},
- {"nativePushAllowFds", "!(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
- {"nativeRestoreAllowFds", "!(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
+ // @FastNative
+ {"nativePushAllowFds", "(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
+ // @FastNative
+ {"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
{"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeNative},
{"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob},
- {"nativeWriteInt", "!(JI)V", (void*)android_os_Parcel_writeInt},
- {"nativeWriteLong", "!(JJ)V", (void*)android_os_Parcel_writeLong},
- {"nativeWriteFloat", "!(JF)V", (void*)android_os_Parcel_writeFloat},
- {"nativeWriteDouble", "!(JD)V", (void*)android_os_Parcel_writeDouble},
+ // @FastNative
+ {"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt},
+ // @FastNative
+ {"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong},
+ // @FastNative
+ {"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat},
+ // @FastNative
+ {"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
{"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
{"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
- {"nativeReadInt", "!(J)I", (void*)android_os_Parcel_readInt},
- {"nativeReadLong", "!(J)J", (void*)android_os_Parcel_readLong},
- {"nativeReadFloat", "!(J)F", (void*)android_os_Parcel_readFloat},
- {"nativeReadDouble", "!(J)D", (void*)android_os_Parcel_readDouble},
+ // @FastNative
+ {"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt},
+ // @FastNative
+ {"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong},
+ // @FastNative
+ {"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat},
+ // @FastNative
+ {"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble},
{"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
{"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
{"nativeReadFileDescriptor", "(J)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
@@ -765,7 +782,8 @@
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
{"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
{"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
- {"nativeHasFileDescriptors", "!(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
+ // @FastNative
+ {"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/jni/android_os_SystemClock.cpp b/core/jni/android_os_SystemClock.cpp
index ccb833a..2fade69 100644
--- a/core/jni/android_os_SystemClock.cpp
+++ b/core/jni/android_os_SystemClock.cpp
@@ -36,29 +36,18 @@
namespace android {
-/*
- * native public static long uptimeMillis();
- */
-static jlong android_os_SystemClock_uptimeMillis(JNIEnv* env,
- jobject clazz)
-{
- return (jlong)uptimeMillis();
-}
-
-/*
- * native public static long elapsedRealtime();
- */
-static jlong android_os_SystemClock_elapsedRealtime(JNIEnv* env,
- jobject clazz)
-{
- return (jlong)elapsedRealtime();
-}
+static_assert(std::is_same<int64_t, jlong>::value, "jlong isn't an int64_t");
+static_assert(std::is_same<decltype(uptimeMillis()), int64_t>::value,
+ "uptimeMillis signature change, expected int64_t return value");
+static_assert(std::is_same<decltype(elapsedRealtime()), int64_t>::value,
+ "uptimeMillis signature change, expected int64_t return value");
+static_assert(std::is_same<decltype(elapsedRealtimeNano()), int64_t>::value,
+ "uptimeMillis signature change, expected int64_t return value");
/*
* native public static long currentThreadTimeMillis();
*/
-static jlong android_os_SystemClock_currentThreadTimeMillis(JNIEnv* env,
- jobject clazz)
+static jlong android_os_SystemClock_currentThreadTimeMillis()
{
struct timespec tm;
@@ -70,8 +59,7 @@
/*
* native public static long currentThreadTimeMicro();
*/
-static jlong android_os_SystemClock_currentThreadTimeMicro(JNIEnv* env,
- jobject clazz)
+static jlong android_os_SystemClock_currentThreadTimeMicro()
{
struct timespec tm;
@@ -83,8 +71,7 @@
/*
* native public static long currentTimeMicro();
*/
-static jlong android_os_SystemClock_currentTimeMicro(JNIEnv* env,
- jobject clazz)
+static jlong android_os_SystemClock_currentTimeMicro()
{
struct timeval tv;
@@ -93,31 +80,22 @@
}
/*
- * public static native long elapsedRealtimeNano();
- */
-static jlong android_os_SystemClock_elapsedRealtimeNano(JNIEnv* env,
- jobject clazz)
-{
- return (jlong)elapsedRealtimeNano();
-}
-
-/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
- /* name, signature, funcPtr */
- { "uptimeMillis", "!()J",
- (void*) android_os_SystemClock_uptimeMillis },
- { "elapsedRealtime", "!()J",
- (void*) android_os_SystemClock_elapsedRealtime },
- { "currentThreadTimeMillis", "!()J",
+ // All of these are @CriticalNative, so we can defer directly to SystemClock.h for
+ // some of these
+ { "uptimeMillis", "()J", (void*) uptimeMillis },
+ { "elapsedRealtime", "()J", (void*) elapsedRealtime },
+ { "elapsedRealtimeNanos", "()J", (void*) elapsedRealtimeNano },
+
+ // SystemClock doesn't have an implementation for these that we can directly call
+ { "currentThreadTimeMillis", "()J",
(void*) android_os_SystemClock_currentThreadTimeMillis },
- { "currentThreadTimeMicro", "!()J",
+ { "currentThreadTimeMicro", "()J",
(void*) android_os_SystemClock_currentThreadTimeMicro },
- { "currentTimeMicro", "!()J",
+ { "currentTimeMicro", "()J",
(void*) android_os_SystemClock_currentTimeMicro },
- { "elapsedRealtimeNanos", "!()J",
- (void*) android_os_SystemClock_elapsedRealtimeNano },
};
int register_android_os_SystemClock(JNIEnv* env)
{
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index 30fc47b..ea893f0 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -110,27 +110,30 @@
{ "nativeGetEnabledTags",
"()J",
(void*)android_os_Trace_nativeGetEnabledTags },
- { "nativeTraceCounter",
- "!(JLjava/lang/String;I)V",
- (void*)android_os_Trace_nativeTraceCounter },
- { "nativeTraceBegin",
- "!(JLjava/lang/String;)V",
- (void*)android_os_Trace_nativeTraceBegin },
- { "nativeTraceEnd",
- "!(J)V",
- (void*)android_os_Trace_nativeTraceEnd },
- { "nativeAsyncTraceBegin",
- "!(JLjava/lang/String;I)V",
- (void*)android_os_Trace_nativeAsyncTraceBegin },
- { "nativeAsyncTraceEnd",
- "!(JLjava/lang/String;I)V",
- (void*)android_os_Trace_nativeAsyncTraceEnd },
{ "nativeSetAppTracingAllowed",
"(Z)V",
(void*)android_os_Trace_nativeSetAppTracingAllowed },
{ "nativeSetTracingEnabled",
"(Z)V",
(void*)android_os_Trace_nativeSetTracingEnabled },
+
+ // ----------- @FastNative ----------------
+
+ { "nativeTraceCounter",
+ "(JLjava/lang/String;I)V",
+ (void*)android_os_Trace_nativeTraceCounter },
+ { "nativeTraceBegin",
+ "(JLjava/lang/String;)V",
+ (void*)android_os_Trace_nativeTraceBegin },
+ { "nativeTraceEnd",
+ "(J)V",
+ (void*)android_os_Trace_nativeTraceEnd },
+ { "nativeAsyncTraceBegin",
+ "(JLjava/lang/String;I)V",
+ (void*)android_os_Trace_nativeAsyncTraceBegin },
+ { "nativeAsyncTraceEnd",
+ "(JLjava/lang/String;I)V",
+ (void*)android_os_Trace_nativeAsyncTraceEnd },
};
int register_android_os_Trace(JNIEnv* env) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index c4c1511..a58bc90 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -31,7 +31,7 @@
#include "androidfw/Asset.h"
#include "androidfw/AssetManager.h"
-#include "androidfw/AttributeFinder.h"
+#include "androidfw/AttributeResolution.h"
#include "androidfw/ResourceTypes.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_util_Binder.h"
@@ -51,7 +51,6 @@
namespace android {
static const bool kThrowOnBadId = false;
-static const bool kDebugStyles = false;
// ----------------------------------------------------------------------------
@@ -98,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);
@@ -175,7 +164,7 @@
}
// Generic idmap parameters
- const char* argv[7];
+ const char* argv[8];
int argc = 0;
struct stat st;
@@ -186,24 +175,24 @@
argv[argc++] = AssetManager::TARGET_APK_PATH;
argv[argc++] = AssetManager::IDMAP_DIR;
- // Directories to scan for overlays: if OVERLAY_SUBDIR_PROPERTY is defined,
- // use OVERLAY_SUBDIR/<value of OVERLAY_SUBDIR_PROPERTY>/ if exists, otherwise
- // use OVERLAY_DIR if exists.
+ // 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_SUBDIR_PROPERTY, subdir);
+ int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
if (len > 0) {
- String8 subdirPath = String8(AssetManager::OVERLAY_SUBDIR) + "/" + subdir;
- if (stat(subdirPath.string(), &st) == 0) {
- argv[argc++] = subdirPath.string();
+ String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
+ if (stat(overlayPath.string(), &st) == 0) {
+ argv[argc++] = overlayPath.string();
}
- } else if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
+ }
+ if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
argv[argc++] = AssetManager::OVERLAY_DIR;
}
// 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);
@@ -1120,30 +1109,6 @@
theme->dumpToLog();
}
-class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, jsize> {
-public:
- explicit XmlAttributeFinder(const ResXMLParser* parser)
- : BackTrackingAttributeFinder(0, parser != NULL ? parser->getAttributeCount() : 0)
- , mParser(parser) {}
-
- inline uint32_t getAttribute(jsize index) const {
- return mParser->getAttributeNameResID(index);
- }
-
-private:
- const ResXMLParser* mParser;
-};
-
-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;
- }
-};
-
static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz,
jlong themeToken,
jint defStyleAttr,
@@ -1166,16 +1131,6 @@
return JNI_FALSE;
}
- if (kDebugStyles) {
- ALOGI("APPLY STYLE: theme=0x%" PRIx64 " defStyleAttr=0x%x "
- "defStyleRes=0x%x", themeToken, defStyleAttr, defStyleRes);
- }
-
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- const ResTable& res = theme->getResTable();
- ResTable_config config;
- Res_value value;
-
const jsize NI = env->GetArrayLength(attrs);
const jsize NV = env->GetArrayLength(outValues);
if (NV < (NI*STYLE_NUM_ENTRIES)) {
@@ -1192,159 +1147,32 @@
const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues);
jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- jint* dest = baseDest;
- if (dest == NULL) {
+ if (baseDest == NULL) {
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
return JNI_FALSE;
}
jint* indices = NULL;
- int indicesIdx = 0;
if (outIndices != NULL) {
if (env->GetArrayLength(outIndices) > NI) {
indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 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;
- }
- }
- }
-
- // 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 (jsize ii=0; ii<NI; ii++) {
- const uint32_t curIdent = (uint32_t)src[ii];
-
- if (kDebugStyles) {
- ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
- }
-
- // 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 (NSV > 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 (kThrowOnBadId) {
- if (newBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return JNI_FALSE;
- }
- }
- 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;
- }
-
- if (kDebugStyles) {
- ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType,
- value.data);
- }
-
- // Write the final value back to Java.
- dest[STYLE_TYPE] = value.dataType;
- dest[STYLE_DATA] = value.data;
- dest[STYLE_ASSET_COOKIE] =
- block != -1 ? reinterpret_cast<jint>(res.getTableCookie(block)) : (jint)-1;
- dest[STYLE_RESOURCE_ID] = resid;
- dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- dest[STYLE_DENSITY] = config.density;
-
- if (indices != NULL && value.dataType != Res_value::TYPE_NULL) {
- indicesIdx++;
- indices[indicesIdx] = ii;
- }
-
- dest += STYLE_NUM_ENTRIES;
- }
-
- res.unlock();
+ ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
+ bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes,
+ (uint32_t*) srcValues, NSV,
+ (uint32_t*) src, NI,
+ (uint32_t*) baseDest,
+ (uint32_t*) indices);
if (indices != NULL) {
- indices[0] = indicesIdx;
env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
}
env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0);
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-
- return JNI_TRUE;
+ return result ? JNI_TRUE : JNI_FALSE;
}
static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,
@@ -1369,18 +1197,6 @@
return JNI_FALSE;
}
- if (kDebugStyles) {
- ALOGI("APPLY STYLE: theme=0x%" PRIx64 " defStyleAttr=0x%x defStyleRes=0x%x "
- "xml=0x%" PRIx64, themeToken, defStyleAttr, defStyleRes,
- xmlParserToken);
- }
-
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- const ResTable& res = theme->getResTable();
- ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
- ResTable_config config;
- Res_value value;
-
const jsize NI = env->GetArrayLength(attrs);
const jsize NV = env->GetArrayLength(outValues);
if (NV < (NI*STYLE_NUM_ENTRIES)) {
@@ -1394,211 +1210,32 @@
}
jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- jint* dest = baseDest;
- if (dest == NULL) {
+ if (baseDest == NULL) {
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
return JNI_FALSE;
}
jint* indices = NULL;
- int indicesIdx = 0;
if (outIndices != NULL) {
if (env->GetArrayLength(outIndices) > NI) {
indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 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 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;
- }
- }
- }
-
- // 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 jsize 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 (jsize ii = 0; ii < NI; ii++) {
- const uint32_t curIdent = (uint32_t)src[ii];
-
- if (kDebugStyles) {
- ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
- }
-
- // 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 jsize 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 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);
- }
- }
- }
-
- 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 (kThrowOnBadId) {
- if (newBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return JNI_FALSE;
- }
- }
-
- 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;
- }
-
- if (kDebugStyles) {
- ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data);
- }
-
- // Write the final value back to Java.
- dest[STYLE_TYPE] = value.dataType;
- dest[STYLE_DATA] = value.data;
- dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ?
- static_cast<jint>(res.getTableCookie(block)) : -1;
- dest[STYLE_RESOURCE_ID] = resid;
- dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- dest[STYLE_DENSITY] = config.density;
-
- if (indices != NULL && value.dataType != Res_value::TYPE_NULL) {
- indicesIdx++;
- indices[indicesIdx] = ii;
- }
-
- dest += STYLE_NUM_ENTRIES;
- }
-
- res.unlock();
+ ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
+ ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
+ bool result = ApplyStyle(theme, xmlParser,
+ defStyleAttr, defStyleRes,
+ (uint32_t*) src, NI,
+ (uint32_t*) baseDest,
+ (uint32_t*) indices);
if (indices != NULL) {
- indices[0] = indicesIdx;
env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
}
env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-
- return JNI_TRUE;
+ return result ? JNI_TRUE : JNI_FALSE;
}
static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz,
@@ -1626,8 +1263,6 @@
}
const ResTable& res(am->getResources());
ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken;
- ResTable_config config;
- Res_value value;
const jsize NI = env->GetArrayLength(attrs);
const jsize NV = env->GetArrayLength(outValues);
@@ -1642,108 +1277,29 @@
}
jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- jint* dest = baseDest;
- if (dest == NULL) {
+ if (baseDest == NULL) {
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
return JNI_FALSE;
}
jint* indices = NULL;
- int indicesIdx = 0;
if (outIndices != NULL) {
if (env->GetArrayLength(outIndices) > NI) {
indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
}
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- // Retrieve the XML attributes, if requested.
- const jsize NX = xmlParser->getAttributeCount();
- jsize ix=0;
- uint32_t curXmlAttr = xmlParser->getAttributeNameResID(ix);
-
- 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 (jsize ii=0; ii<NI; ii++) {
- const uint32_t curIdent = (uint32_t)src[ii];
-
- // 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;
-
- // 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 (kThrowOnBadId) {
- if (newBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return JNI_FALSE;
- }
- }
- 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.
- dest[STYLE_TYPE] = value.dataType;
- dest[STYLE_DATA] = value.data;
- dest[STYLE_ASSET_COOKIE] =
- block != kXmlBlock ? reinterpret_cast<jint>(res.getTableCookie(block)) : (jint)-1;
- dest[STYLE_RESOURCE_ID] = resid;
- dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- dest[STYLE_DENSITY] = config.density;
-
- if (indices != NULL && value.dataType != Res_value::TYPE_NULL) {
- indicesIdx++;
- indices[indicesIdx] = ii;
- }
-
- dest += STYLE_NUM_ENTRIES;
- }
-
- res.unlock();
+ bool result = RetrieveAttributes(&res, xmlParser,
+ (uint32_t*) src, NI,
+ (uint32_t*) baseDest,
+ (uint32_t*) indices);
if (indices != NULL) {
- indices[0] = indicesIdx;
env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
}
-
env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-
- return JNI_TRUE;
+ return result ? JNI_TRUE : JNI_FALSE;
}
static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
@@ -2160,9 +1716,11 @@
(void*) android_content_AssetManager_readAsset },
{ "seekAsset", "(JJI)J",
(void*) android_content_AssetManager_seekAsset },
- { "getAssetLength", "!(J)J",
+ // @FastNative
+ { "getAssetLength", "(J)J",
(void*) android_content_AssetManager_getAssetLength },
- { "getAssetRemainingLength", "!(J)J",
+ // @FastNative
+ { "getAssetRemainingLength", "(J)J",
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;Z)I",
(void*) android_content_AssetManager_addAssetPath },
@@ -2178,25 +1736,35 @@
(void*) android_content_AssetManager_getNonSystemLocales },
{ "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
(void*) android_content_AssetManager_getSizeConfigurations },
- { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
+ // @FastNative
+ { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
(void*) android_content_AssetManager_setConfiguration },
- { "getResourceIdentifier","!(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ // @FastNative
+ { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_getResourceIdentifier },
- { "getResourceName","!(I)Ljava/lang/String;",
+ // @FastNative
+ { "getResourceName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceName },
- { "getResourcePackageName","!(I)Ljava/lang/String;",
+ // @FastNative
+ { "getResourcePackageName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourcePackageName },
- { "getResourceTypeName","!(I)Ljava/lang/String;",
+ // @FastNative
+ { "getResourceTypeName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceTypeName },
- { "getResourceEntryName","!(I)Ljava/lang/String;",
+ // @FastNative
+ { "getResourceEntryName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceEntryName },
- { "loadResourceValue","!(ISLandroid/util/TypedValue;Z)I",
+ // @FastNative
+ { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceValue },
- { "loadResourceBagValue","!(IILandroid/util/TypedValue;Z)I",
+ // @FastNative
+ { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceBagValue },
- { "getStringBlockCount","!()I",
+ // @FastNative
+ { "getStringBlockCount","()I",
(void*) android_content_AssetManager_getStringBlockCount },
- { "getNativeStringBlock","!(I)J",
+ // @FastNative
+ { "getNativeStringBlock","(I)J",
(void*) android_content_AssetManager_getNativeStringBlock },
{ "getCookieName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getCookieName },
@@ -2214,21 +1782,28 @@
(void*) android_content_AssetManager_copyTheme },
{ "clearTheme", "(J)V",
(void*) android_content_AssetManager_clearTheme },
- { "loadThemeAttributeValue", "!(JILandroid/util/TypedValue;Z)I",
+ // @FastNative
+ { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadThemeAttributeValue },
- { "getThemeChangingConfigurations", "!(J)I",
+ // @FastNative
+ { "getThemeChangingConfigurations", "(J)I",
(void*) android_content_AssetManager_getThemeChangingConfigurations },
{ "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
(void*) android_content_AssetManager_dumpTheme },
- { "applyStyle","!(JIIJ[I[I[I)Z",
+ // @FastNative
+ { "applyStyle","(JIIJ[I[I[I)Z",
(void*) android_content_AssetManager_applyStyle },
- { "resolveAttrs","!(JII[I[I[I[I)Z",
+ // @FastNative
+ { "resolveAttrs","(JII[I[I[I[I)Z",
(void*) android_content_AssetManager_resolveAttrs },
- { "retrieveAttributes","!(J[I[I[I)Z",
+ // @FastNative
+ { "retrieveAttributes","(J[I[I[I)Z",
(void*) android_content_AssetManager_retrieveAttributes },
- { "getArraySize","!(I)I",
+ // @FastNative
+ { "getArraySize","(I)I",
(void*) android_content_AssetManager_getArraySize },
- { "retrieveArray","!(I[I)I",
+ // @FastNative
+ { "retrieveArray","(I[I)I",
(void*) android_content_AssetManager_retrieveArray },
// XML files.
@@ -2238,11 +1813,14 @@
// Arrays.
{ "getArrayStringResource","(I)[Ljava/lang/String;",
(void*) android_content_AssetManager_getArrayStringResource },
- { "getArrayStringInfo","!(I)[I",
+ // @FastNative
+ { "getArrayStringInfo","(I)[I",
(void*) android_content_AssetManager_getArrayStringInfo },
- { "getArrayIntResource","!(I)[I",
+ // @FastNative
+ { "getArrayIntResource","(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
- { "getStyleAttributes","!(I)[I",
+ // @FastNative
+ { "getStyleAttributes","(I)[I",
(void*) android_content_AssetManager_getStyleAttributes },
// Bookkeeping.
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 4f8a2cb..173afd8 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -19,7 +19,7 @@
#include "JNIHelp.h"
#include "core_jni_helpers.h"
#include "jni.h"
-#include "log/logger.h"
+#include <log/logger.h>
#define UNUSED __attribute__((__unused__))
diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp
index 53669a8..10efb95 100644
--- a/core/jni/android_util_PathParser.cpp
+++ b/core/jni/android_util_PathParser.cpp
@@ -101,14 +101,17 @@
static const JNINativeMethod gMethods[] = {
{"nParseStringForPath", "(JLjava/lang/String;I)V", (void*)parseStringForPath},
- {"nCreateEmptyPathData", "!()J", (void*)createEmptyPathData},
- {"nCreatePathData", "!(J)J", (void*)createPathData},
{"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath},
- {"nInterpolatePathData", "!(JJJF)Z", (void*)interpolatePathData},
- {"nFinalize", "!(J)V", (void*)deletePathData},
- {"nCanMorph", "!(JJ)Z", (void*)canMorphPathData},
- {"nSetPathData", "!(JJ)V", (void*)setPathData},
- {"nCreatePathFromPathData", "!(JJ)V", (void*)setSkPathFromPathData},
+
+ // ---------------- @FastNative -----------------
+
+ {"nCreateEmptyPathData", "()J", (void*)createEmptyPathData},
+ {"nCreatePathData", "(J)J", (void*)createPathData},
+ {"nInterpolatePathData", "(JJJF)Z", (void*)interpolatePathData},
+ {"nFinalize", "(J)V", (void*)deletePathData},
+ {"nCanMorph", "(JJ)Z", (void*)canMorphPathData},
+ {"nSetPathData", "(JJ)V", (void*)setPathData},
+ {"nCreatePathFromPathData", "(JJ)V", (void*)setSkPathFromPathData},
};
int register_android_util_PathParser(JNIEnv* env) {
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index a15c23c..99882cc 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -372,42 +372,45 @@
(void*) android_content_XmlBlock_nativeGetStringBlock },
{ "nativeCreateParseState", "(J)J",
(void*) android_content_XmlBlock_nativeCreateParseState },
- { "nativeNext", "!(J)I",
- (void*) android_content_XmlBlock_nativeNext },
- { "nativeGetNamespace", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetNamespace },
- { "nativeGetName", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetName },
- { "nativeGetText", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetText },
- { "nativeGetLineNumber", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetLineNumber },
- { "nativeGetAttributeCount", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetAttributeCount },
- { "nativeGetAttributeNamespace","!(JI)I",
- (void*) android_content_XmlBlock_nativeGetAttributeNamespace },
- { "nativeGetAttributeName", "!(JI)I",
- (void*) android_content_XmlBlock_nativeGetAttributeName },
- { "nativeGetAttributeResource", "!(JI)I",
- (void*) android_content_XmlBlock_nativeGetAttributeResource },
- { "nativeGetAttributeDataType", "!(JI)I",
- (void*) android_content_XmlBlock_nativeGetAttributeDataType },
- { "nativeGetAttributeData", "!(JI)I",
- (void*) android_content_XmlBlock_nativeGetAttributeData },
- { "nativeGetAttributeStringValue", "!(JI)I",
- (void*) android_content_XmlBlock_nativeGetAttributeStringValue },
- { "nativeGetAttributeIndex", "!(JLjava/lang/String;Ljava/lang/String;)I",
- (void*) android_content_XmlBlock_nativeGetAttributeIndex },
- { "nativeGetIdAttribute", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetIdAttribute },
- { "nativeGetClassAttribute", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetClassAttribute },
- { "nativeGetStyleAttribute", "!(J)I",
- (void*) android_content_XmlBlock_nativeGetStyleAttribute },
{ "nativeDestroyParseState", "(J)V",
(void*) android_content_XmlBlock_nativeDestroyParseState },
{ "nativeDestroy", "(J)V",
(void*) android_content_XmlBlock_nativeDestroy },
+
+ // ------------------- @FastNative ----------------------
+
+ { "nativeNext", "(J)I",
+ (void*) android_content_XmlBlock_nativeNext },
+ { "nativeGetNamespace", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetNamespace },
+ { "nativeGetName", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetName },
+ { "nativeGetText", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetText },
+ { "nativeGetLineNumber", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetLineNumber },
+ { "nativeGetAttributeCount", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeCount },
+ { "nativeGetAttributeNamespace","(JI)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeNamespace },
+ { "nativeGetAttributeName", "(JI)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeName },
+ { "nativeGetAttributeResource", "(JI)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeResource },
+ { "nativeGetAttributeDataType", "(JI)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeDataType },
+ { "nativeGetAttributeData", "(JI)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeData },
+ { "nativeGetAttributeStringValue", "(JI)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeStringValue },
+ { "nativeGetAttributeIndex", "(JLjava/lang/String;Ljava/lang/String;)I",
+ (void*) android_content_XmlBlock_nativeGetAttributeIndex },
+ { "nativeGetIdAttribute", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetIdAttribute },
+ { "nativeGetClassAttribute", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetClassAttribute },
+ { "nativeGetStyleAttribute", "(J)I",
+ (void*) android_content_XmlBlock_nativeGetStyleAttribute },
};
int register_android_content_XmlBlock(JNIEnv* env)
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index ea5a760..2eada3e 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -161,7 +161,8 @@
{ "nativeDispose",
"(J)V",
(void*)nativeDispose },
- { "nativeScheduleVsync", "!(J)V",
+ // @FastNative
+ { "nativeScheduleVsync", "(J)V",
(void*)nativeScheduleVsync }
};
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index edc0da3..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);
}
@@ -215,24 +217,27 @@
const char* const kClassPathName = "android/view/DisplayListCanvas";
static JNINativeMethod gMethods[] = {
- { "nInsertReorderBarrier","!(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
- { "nCallDrawGLFunction", "!(JJLjava/lang/Runnable;)V",
+ // ------------ @FastNative ------------------
+
+ { "nInsertReorderBarrier","(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
+
+ { "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V",
(void*) android_view_DisplayListCanvas_callDrawGLFunction },
- { "nDrawRoundRect", "!(JJJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRoundRectProps },
- { "nDrawCircle", "!(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
+ { "nDrawRoundRect", "(JJJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRoundRectProps },
+ { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
- { "nFinishRecording", "!(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
- { "nDrawRenderNode", "!(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
+ { "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 },
+ { "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer },
- { "nGetMaximumTextureWidth", "!()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
- { "nGetMaximumTextureHeight", "!()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
+ { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
+ { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
};
static JNINativeMethod gActivityThreadMethods[] = {
diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp
index 59c337b..1743731 100644
--- a/core/jni/android_view_GraphicBuffer.cpp
+++ b/core/jni/android_view_GraphicBuffer.cpp
@@ -182,7 +182,8 @@
SkBitmap bitmap;
bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
convertPixelFormat(buffer->getPixelFormat()),
- kPremul_SkAlphaType),
+ kPremul_SkAlphaType,
+ GraphicsJNI::defaultColorSpace()),
bytesCount);
if (buffer->getWidth() > 0 && buffer->getHeight() > 0) {
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 0245d38..2132f3d 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -383,17 +383,6 @@
return 0;
}
-static jlong android_view_MotionEvent_nativeCopy(JNIEnv* env, jclass clazz,
- jlong destNativePtr, jlong sourceNativePtr, jboolean keepHistory) {
- MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
- if (!destEvent) {
- destEvent = new MotionEvent();
- }
- MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr);
- destEvent->copyFrom(sourceEvent, keepHistory);
- return reinterpret_cast<jlong>(destEvent);
-}
-
static void android_view_MotionEvent_nativeDispose(JNIEnv* env, jclass clazz,
jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
@@ -426,228 +415,6 @@
event->setMetaState(event->getMetaState() | metaState);
}
-static jint android_view_MotionEvent_nativeGetDeviceId(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getDeviceId();
-}
-
-static jint android_view_MotionEvent_nativeGetSource(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getSource();
-}
-
-static void android_view_MotionEvent_nativeSetSource(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint source) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setSource(source);
-}
-
-static jint android_view_MotionEvent_nativeGetAction(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getAction();
-}
-
-static void android_view_MotionEvent_nativeSetAction(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint action) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setAction(action);
-}
-
-static int android_view_MotionEvent_nativeGetActionButton(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getActionButton();
-}
-
-static void android_view_MotionEvent_nativeSetActionButton(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint button) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setActionButton(button);
-}
-
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->isTouchEvent();
-}
-
-static jint android_view_MotionEvent_nativeGetFlags(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getFlags();
-}
-
-static void android_view_MotionEvent_nativeSetFlags(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint flags) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setFlags(flags);
-}
-
-static jint android_view_MotionEvent_nativeGetEdgeFlags(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getEdgeFlags();
-}
-
-static void android_view_MotionEvent_nativeSetEdgeFlags(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint edgeFlags) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setEdgeFlags(edgeFlags);
-}
-
-static jint android_view_MotionEvent_nativeGetMetaState(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getMetaState();
-}
-
-static jint android_view_MotionEvent_nativeGetButtonState(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getButtonState();
-}
-
-static void android_view_MotionEvent_nativeSetButtonState(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint buttonState) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setButtonState(buttonState);
-}
-
-static void android_view_MotionEvent_nativeOffsetLocation(JNIEnv* env, jclass clazz,
- jlong nativePtr, jfloat deltaX, jfloat deltaY) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->offsetLocation(deltaX, deltaY);
-}
-
-static jfloat android_view_MotionEvent_nativeGetXOffset(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getXOffset();
-}
-
-static jfloat android_view_MotionEvent_nativeGetYOffset(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getYOffset();
-}
-
-static jfloat android_view_MotionEvent_nativeGetXPrecision(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getXPrecision();
-}
-
-static jfloat android_view_MotionEvent_nativeGetYPrecision(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getYPrecision();
-}
-
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getDownTime();
-}
-
-static void android_view_MotionEvent_nativeSetDownTimeNanos(JNIEnv* env, jclass clazz,
- jlong nativePtr, jlong downTimeNanos) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->setDownTime(downTimeNanos);
-}
-
-static jint android_view_MotionEvent_nativeGetPointerCount(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return jint(event->getPointerCount());
-}
-
-static jint android_view_MotionEvent_nativeGetPointerId(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint pointerIndex) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- size_t pointerCount = event->getPointerCount();
- if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
- return -1;
- }
- return event->getPointerId(pointerIndex);
-}
-
-static jint android_view_MotionEvent_nativeGetToolType(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint pointerIndex) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- size_t pointerCount = event->getPointerCount();
- if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
- return -1;
- }
- return event->getToolType(pointerIndex);
-}
-
-static jint android_view_MotionEvent_nativeFindPointerIndex(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint pointerId) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return jint(event->findPointerIndex(pointerId));
-}
-
-static jint android_view_MotionEvent_nativeGetHistorySize(JNIEnv* env, jclass clazz,
- jlong nativePtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return jint(event->getHistorySize());
-}
-
-static jlong android_view_MotionEvent_nativeGetEventTimeNanos(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint historyPos) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- if (historyPos == HISTORY_CURRENT) {
- return event->getEventTime();
- } else {
- size_t historySize = event->getHistorySize();
- if (!validateHistoryPos(env, historyPos, historySize)) {
- return 0;
- }
- return event->getHistoricalEventTime(historyPos);
- }
-}
-
-static jfloat android_view_MotionEvent_nativeGetRawAxisValue(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint axis, jint pointerIndex, jint historyPos) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- size_t pointerCount = event->getPointerCount();
- if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
- return 0;
- }
-
- if (historyPos == HISTORY_CURRENT) {
- return event->getRawAxisValue(axis, pointerIndex);
- } else {
- size_t historySize = event->getHistorySize();
- if (!validateHistoryPos(env, historyPos, historySize)) {
- return 0;
- }
- return event->getHistoricalRawAxisValue(axis, pointerIndex, historyPos);
- }
-}
-
-static jfloat android_view_MotionEvent_nativeGetAxisValue(JNIEnv* env, jclass clazz,
- jlong nativePtr, jint axis, jint pointerIndex, jint historyPos) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- size_t pointerCount = event->getPointerCount();
- if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
- return 0;
- }
-
- if (historyPos == HISTORY_CURRENT) {
- return event->getAxisValue(axis, pointerIndex);
- } else {
- size_t historySize = event->getHistorySize();
- if (!validateHistoryPos(env, historyPos, historySize)) {
- return 0;
- }
- return event->getHistoricalAxisValue(axis, pointerIndex, historyPos);
- }
-}
-
static void android_view_MotionEvent_nativeGetPointerCoords(JNIEnv* env, jclass clazz,
jlong nativePtr, jint pointerIndex, jint historyPos, jobject outPointerCoordsObj) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
@@ -684,30 +451,6 @@
pointerPropertiesFromNative(env, pointerProperties, outPointerPropertiesObj);
}
-static void android_view_MotionEvent_nativeScale(JNIEnv* env, jclass clazz,
- jlong nativePtr, jfloat scale) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- event->scale(scale);
-}
-
-static void android_view_MotionEvent_nativeTransform(JNIEnv* env, jclass clazz,
- jlong nativePtr, jobject matrixObj) {
- SkMatrix* matrix = android_graphics_Matrix_getSkMatrix(env, matrixObj);
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-
- float m[9];
- m[0] = SkScalarToFloat(matrix->get(SkMatrix::kMScaleX));
- m[1] = SkScalarToFloat(matrix->get(SkMatrix::kMSkewX));
- m[2] = SkScalarToFloat(matrix->get(SkMatrix::kMTransX));
- m[3] = SkScalarToFloat(matrix->get(SkMatrix::kMSkewY));
- m[4] = SkScalarToFloat(matrix->get(SkMatrix::kMScaleY));
- m[5] = SkScalarToFloat(matrix->get(SkMatrix::kMTransY));
- m[6] = SkScalarToFloat(matrix->get(SkMatrix::kMPersp0));
- m[7] = SkScalarToFloat(matrix->get(SkMatrix::kMPersp1));
- m[8] = SkScalarToFloat(matrix->get(SkMatrix::kMPersp2));
- event->transform(m);
-}
-
static jlong android_view_MotionEvent_nativeReadFromParcel(JNIEnv* env, jclass clazz,
jlong nativePtr, jobject parcelObj) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
@@ -750,6 +493,243 @@
return static_cast<jint>(MotionEvent::getAxisFromLabel(axisLabel.c_str()));
}
+// ---------------- @FastNative ----------------------------------
+
+static jint android_view_MotionEvent_nativeGetPointerId(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint pointerIndex) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ size_t pointerCount = event->getPointerCount();
+ if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
+ return -1;
+ }
+ return event->getPointerId(pointerIndex);
+}
+
+static jint android_view_MotionEvent_nativeGetToolType(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint pointerIndex) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ size_t pointerCount = event->getPointerCount();
+ if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
+ return -1;
+ }
+ return event->getToolType(pointerIndex);
+}
+
+static jlong android_view_MotionEvent_nativeGetEventTimeNanos(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint historyPos) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ if (historyPos == HISTORY_CURRENT) {
+ return event->getEventTime();
+ } else {
+ size_t historySize = event->getHistorySize();
+ if (!validateHistoryPos(env, historyPos, historySize)) {
+ return 0;
+ }
+ return event->getHistoricalEventTime(historyPos);
+ }
+}
+
+static jfloat android_view_MotionEvent_nativeGetRawAxisValue(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint axis,
+ jint pointerIndex, jint historyPos) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ size_t pointerCount = event->getPointerCount();
+ if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
+ return 0;
+ }
+
+ if (historyPos == HISTORY_CURRENT) {
+ return event->getRawAxisValue(axis, pointerIndex);
+ } else {
+ size_t historySize = event->getHistorySize();
+ if (!validateHistoryPos(env, historyPos, historySize)) {
+ return 0;
+ }
+ return event->getHistoricalRawAxisValue(axis, pointerIndex, historyPos);
+ }
+}
+
+static jfloat android_view_MotionEvent_nativeGetAxisValue(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint axis, jint pointerIndex, jint historyPos) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ size_t pointerCount = event->getPointerCount();
+ if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
+ return 0;
+ }
+
+ if (historyPos == HISTORY_CURRENT) {
+ return event->getAxisValue(axis, pointerIndex);
+ } else {
+ size_t historySize = event->getHistorySize();
+ if (!validateHistoryPos(env, historyPos, historySize)) {
+ return 0;
+ }
+ return event->getHistoricalAxisValue(axis, pointerIndex, historyPos);
+ }
+}
+
+// ----------------- @CriticalNative ------------------------------
+
+static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
+ jboolean keepHistory) {
+ MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
+ if (!destEvent) {
+ destEvent = new MotionEvent();
+ }
+ MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr);
+ destEvent->copyFrom(sourceEvent, keepHistory);
+ return reinterpret_cast<jlong>(destEvent);
+}
+
+static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getDeviceId();
+}
+
+static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getSource();
+}
+
+static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setSource(source);
+}
+
+static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getAction();
+}
+
+static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setAction(action);
+}
+
+static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getActionButton();
+}
+
+static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setActionButton(button);
+}
+
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->isTouchEvent();
+}
+
+static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getFlags();
+}
+
+static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setFlags(flags);
+}
+
+static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getEdgeFlags();
+}
+
+static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setEdgeFlags(edgeFlags);
+}
+
+static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getMetaState();
+}
+
+static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getButtonState();
+}
+
+static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setButtonState(buttonState);
+}
+
+static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
+ jfloat deltaY) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->offsetLocation(deltaX, deltaY);
+}
+
+static jfloat android_view_MotionEvent_nativeGetXOffset(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getXOffset();
+}
+
+static jfloat android_view_MotionEvent_nativeGetYOffset(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getYOffset();
+}
+
+static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getXPrecision();
+}
+
+static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getYPrecision();
+}
+
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return event->getDownTime();
+}
+
+static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->setDownTime(downTimeNanos);
+}
+
+static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return jint(event->getPointerCount());
+}
+
+static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return jint(event->findPointerIndex(pointerId));
+}
+
+static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ return jint(event->getHistorySize());
+}
+
+static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ event->scale(scale);
+}
+
+static void android_view_MotionEvent_nativeTransform(jlong nativePtr, jlong matrixPtr) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+ SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
+
+ static_assert(SkMatrix::kMScaleX == 0, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMSkewX == 1, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMTransX == 2, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMSkewY == 3, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMScaleY == 4, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMTransY == 5, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMPersp0 == 6, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMPersp1 == 7, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMPersp2 == 8, "SkMatrix unexpected index");
+ float m[9];
+ matrix->get9(m);
+ event->transform(m);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMotionEventMethods[] = {
@@ -758,117 +738,12 @@
"(JIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
"[Landroid/view/MotionEvent$PointerCoords;)J",
(void*)android_view_MotionEvent_nativeInitialize },
- { "nativeCopy",
- "(JJZ)J",
- (void*)android_view_MotionEvent_nativeCopy },
{ "nativeDispose",
"(J)V",
(void*)android_view_MotionEvent_nativeDispose },
{ "nativeAddBatch",
"(JJ[Landroid/view/MotionEvent$PointerCoords;I)V",
(void*)android_view_MotionEvent_nativeAddBatch },
- { "nativeGetDeviceId",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetDeviceId },
- { "nativeGetSource",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetSource },
- { "nativeSetSource",
- "!(JI)I",
- (void*)android_view_MotionEvent_nativeSetSource },
- { "nativeGetAction",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetAction },
- { "nativeSetAction",
- "!(JI)V",
- (void*)android_view_MotionEvent_nativeSetAction },
- { "nativeGetActionButton",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetActionButton},
- { "nativeSetActionButton",
- "!(JI)V",
- (void*)android_view_MotionEvent_nativeSetActionButton},
- { "nativeIsTouchEvent",
- "!(J)Z",
- (void*)android_view_MotionEvent_nativeIsTouchEvent },
- { "nativeGetFlags",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetFlags },
- { "nativeSetFlags",
- "!(JI)V",
- (void*)android_view_MotionEvent_nativeSetFlags },
- { "nativeGetEdgeFlags",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetEdgeFlags },
- { "nativeSetEdgeFlags",
- "!(JI)V",
- (void*)android_view_MotionEvent_nativeSetEdgeFlags },
- { "nativeGetMetaState",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetMetaState },
- { "nativeGetButtonState",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetButtonState },
- { "nativeSetButtonState",
- "!(JI)V",
- (void*)android_view_MotionEvent_nativeSetButtonState },
- { "nativeOffsetLocation",
- "!(JFF)V",
- (void*)android_view_MotionEvent_nativeOffsetLocation },
- { "nativeGetXOffset",
- "!(J)F",
- (void*)android_view_MotionEvent_nativeGetXOffset },
- { "nativeGetYOffset",
- "!(J)F",
- (void*)android_view_MotionEvent_nativeGetYOffset },
- { "nativeGetXPrecision",
- "!(J)F",
- (void*)android_view_MotionEvent_nativeGetXPrecision },
- { "nativeGetYPrecision",
- "!(J)F",
- (void*)android_view_MotionEvent_nativeGetYPrecision },
- { "nativeGetDownTimeNanos",
- "!(J)J",
- (void*)android_view_MotionEvent_nativeGetDownTimeNanos },
- { "nativeSetDownTimeNanos",
- "!(JJ)V",
- (void*)android_view_MotionEvent_nativeSetDownTimeNanos },
- { "nativeGetPointerCount",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetPointerCount },
- { "nativeGetPointerId",
- "!(JI)I",
- (void*)android_view_MotionEvent_nativeGetPointerId },
- { "nativeGetToolType",
- "!(JI)I",
- (void*)android_view_MotionEvent_nativeGetToolType },
- { "nativeFindPointerIndex",
- "!(JI)I",
- (void*)android_view_MotionEvent_nativeFindPointerIndex },
- { "nativeGetHistorySize",
- "!(J)I",
- (void*)android_view_MotionEvent_nativeGetHistorySize },
- { "nativeGetEventTimeNanos",
- "!(JI)J",
- (void*)android_view_MotionEvent_nativeGetEventTimeNanos },
- { "nativeGetRawAxisValue",
- "!(JIII)F",
- (void*)android_view_MotionEvent_nativeGetRawAxisValue },
- { "nativeGetAxisValue",
- "!(JIII)F",
- (void*)android_view_MotionEvent_nativeGetAxisValue },
- { "nativeGetPointerCoords",
- "(JIILandroid/view/MotionEvent$PointerCoords;)V",
- (void*)android_view_MotionEvent_nativeGetPointerCoords },
- { "nativeGetPointerProperties",
- "(JILandroid/view/MotionEvent$PointerProperties;)V",
- (void*)android_view_MotionEvent_nativeGetPointerProperties },
- { "nativeScale",
- "!(JF)V",
- (void*)android_view_MotionEvent_nativeScale },
- { "nativeTransform",
- "(JLandroid/graphics/Matrix;)V",
- (void*)android_view_MotionEvent_nativeTransform },
{ "nativeReadFromParcel",
"(JLandroid/os/Parcel;)J",
(void*)android_view_MotionEvent_nativeReadFromParcel },
@@ -879,6 +754,116 @@
(void*)android_view_MotionEvent_nativeAxisToString },
{ "nativeAxisFromString", "(Ljava/lang/String;)I",
(void*)android_view_MotionEvent_nativeAxisFromString },
+ { "nativeGetPointerProperties",
+ "(JILandroid/view/MotionEvent$PointerProperties;)V",
+ (void*)android_view_MotionEvent_nativeGetPointerProperties },
+ { "nativeGetPointerCoords",
+ "(JIILandroid/view/MotionEvent$PointerCoords;)V",
+ (void*)android_view_MotionEvent_nativeGetPointerCoords },
+
+ // --------------- @FastNative ----------------------
+ { "nativeGetPointerId",
+ "(JI)I",
+ (void*)android_view_MotionEvent_nativeGetPointerId },
+ { "nativeGetToolType",
+ "(JI)I",
+ (void*)android_view_MotionEvent_nativeGetToolType },
+ { "nativeGetEventTimeNanos",
+ "(JI)J",
+ (void*)android_view_MotionEvent_nativeGetEventTimeNanos },
+ { "nativeGetRawAxisValue",
+ "(JIII)F",
+ (void*)android_view_MotionEvent_nativeGetRawAxisValue },
+ { "nativeGetAxisValue",
+ "(JIII)F",
+ (void*)android_view_MotionEvent_nativeGetAxisValue },
+
+ // --------------- @CriticalNative ------------------
+
+ { "nativeCopy",
+ "(JJZ)J",
+ (void*)android_view_MotionEvent_nativeCopy },
+ { "nativeGetDeviceId",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetDeviceId },
+ { "nativeGetSource",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetSource },
+ { "nativeSetSource",
+ "(JI)I",
+ (void*)android_view_MotionEvent_nativeSetSource },
+ { "nativeGetAction",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetAction },
+ { "nativeSetAction",
+ "(JI)V",
+ (void*)android_view_MotionEvent_nativeSetAction },
+ { "nativeGetActionButton",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetActionButton},
+ { "nativeSetActionButton",
+ "(JI)V",
+ (void*)android_view_MotionEvent_nativeSetActionButton},
+ { "nativeIsTouchEvent",
+ "(J)Z",
+ (void*)android_view_MotionEvent_nativeIsTouchEvent },
+ { "nativeGetFlags",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetFlags },
+ { "nativeSetFlags",
+ "(JI)V",
+ (void*)android_view_MotionEvent_nativeSetFlags },
+ { "nativeGetEdgeFlags",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetEdgeFlags },
+ { "nativeSetEdgeFlags",
+ "(JI)V",
+ (void*)android_view_MotionEvent_nativeSetEdgeFlags },
+ { "nativeGetMetaState",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetMetaState },
+ { "nativeGetButtonState",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetButtonState },
+ { "nativeSetButtonState",
+ "(JI)V",
+ (void*)android_view_MotionEvent_nativeSetButtonState },
+ { "nativeOffsetLocation",
+ "(JFF)V",
+ (void*)android_view_MotionEvent_nativeOffsetLocation },
+ { "nativeGetXOffset",
+ "(J)F",
+ (void*)android_view_MotionEvent_nativeGetXOffset },
+ { "nativeGetYOffset",
+ "(J)F",
+ (void*)android_view_MotionEvent_nativeGetYOffset },
+ { "nativeGetXPrecision",
+ "(J)F",
+ (void*)android_view_MotionEvent_nativeGetXPrecision },
+ { "nativeGetYPrecision",
+ "(J)F",
+ (void*)android_view_MotionEvent_nativeGetYPrecision },
+ { "nativeGetDownTimeNanos",
+ "(J)J",
+ (void*)android_view_MotionEvent_nativeGetDownTimeNanos },
+ { "nativeSetDownTimeNanos",
+ "(JJ)V",
+ (void*)android_view_MotionEvent_nativeSetDownTimeNanos },
+ { "nativeGetPointerCount",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetPointerCount },
+ { "nativeFindPointerIndex",
+ "(JI)I",
+ (void*)android_view_MotionEvent_nativeFindPointerIndex },
+ { "nativeGetHistorySize",
+ "(J)I",
+ (void*)android_view_MotionEvent_nativeGetHistorySize },
+ { "nativeScale",
+ "(JF)V",
+ (void*)android_view_MotionEvent_nativeScale },
+ { "nativeTransform",
+ "(JJ)V",
+ (void*)android_view_MotionEvent_nativeTransform },
};
int register_android_view_MotionEvent(JNIEnv* env) {
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 3f2b924..b6c81cf8 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -318,11 +318,11 @@
return 0;
}
-
SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
convertPixelFormat(outBuffer.format),
- outBuffer.format == PIXEL_FORMAT_RGBX_8888 ?
- kOpaque_SkAlphaType : kPremul_SkAlphaType);
+ outBuffer.format == PIXEL_FORMAT_RGBX_8888
+ ? kOpaque_SkAlphaType : kPremul_SkAlphaType,
+ GraphicsJNI::defaultColorSpace());
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 73b3f52..5b88181 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -175,7 +175,9 @@
}
SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
screenshot->getHeight(),
- colorType, alphaType);
+ colorType,
+ alphaType,
+ GraphicsJNI::defaultColorSpace());
const size_t rowBytes =
screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
@@ -184,14 +186,13 @@
return NULL;
}
- Bitmap* bitmap = new Bitmap(
+ auto bitmap = new Bitmap(
(void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot,
screenshotInfo, rowBytes, nullptr);
screenshot.release();
- bitmap->peekAtPixelRef()->setImmutable();
-
- return GraphicsJNI::createBitmap(env, bitmap,
- GraphicsJNI::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/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index e185281..268aec5 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -90,7 +90,8 @@
default:
break;
}
- return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType);
+ return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType,
+ GraphicsJNI::defaultColorSpace());
}
/**
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 649cc1e..14dcb3f 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -613,21 +613,6 @@
return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
}
-static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) {
- sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
- jsize len = env->GetArrayLength(atlasMapArray);
- if (len <= 0) {
- ALOGW("Failed to initialize atlas, invalid map length: %d", len);
- return;
- }
- int64_t* map = new int64_t[len];
- env->GetLongArrayRegion(atlasMapArray, 0, len, map);
-
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setTextureAtlas(buffer, map, len);
-}
-
static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz,
jlong proxyPtr, jint fd) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -954,8 +939,7 @@
const char* const kClassPathName = "android/view/ThreadedRenderer";
static const JNINativeMethod gMethods[] = {
- { "nSupportsOpenGL", "!()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL },
- { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas },
+ { "nSupportsOpenGL", "()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL },
{ "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
{ "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
diff --git a/core/jni/fd_utils-inl.h b/core/jni/fd_utils-inl.h
index 5c17b23..af27069 100644
--- a/core/jni/fd_utils-inl.h
+++ b/core/jni/fd_utils-inl.h
@@ -260,16 +260,13 @@
// Whitelist files needed for Runtime Resource Overlay, like these:
// /system/vendor/overlay/framework-res.apk
- // /system/vendor/overlay-subdir/pg/framework-res.apk
+ // /system/vendor/overlay/PG/android-framework-runtime-resource-overlay.apk
// /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
- // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
- // See AssetManager.cpp for more details on overlay-subdir.
+ // /data/resource-cache/system@vendor@overlay@PG@framework-res.apk@idmap
static const char* kOverlayDir = "/system/vendor/overlay/";
- static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/";
static const char* kApkSuffix = ".apk";
- if ((android::base::StartsWith(path, kOverlayDir)
- || android::base::StartsWith(path, kOverlaySubdir))
+ if (android::base::StartsWith(path, kOverlayDir)
&& android::base::EndsWith(path, kApkSuffix)
&& path.find("/../") == std::string::npos) {
return true;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 357d6f9..de6f2c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -149,6 +149,8 @@
<protected-broadcast
android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" />
<protected-broadcast
+ android:name="android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
@@ -290,6 +292,7 @@
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
+ <protected-broadcast android:name="android.net.wifi.nan.action.WIFI_NAN_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
<protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
@@ -433,6 +436,7 @@
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
+ <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
<protected-broadcast android:name="wifi_scan_available" />
<protected-broadcast android:name="action.cne.started" />
@@ -495,6 +499,8 @@
<protected-broadcast android:name="com.android.server.retaildemo.ACTION_RESET_DEMO" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
@@ -1706,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"
@@ -3039,7 +3040,7 @@
<!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
@hide -->
<permission android:name="android.permission.PEERS_MAC_ADDRESS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|setup" />
<!-- Allows the Nfc stack to dispatch Nfc messages to applications. Applications
can use this permission to ensure incoming Nfc messages are from the Nfc stack
diff --git a/core/res/res/color/background_cache_hint_selector_device_default.xml b/core/res/res/color/background_cache_hint_selector_device_default.xml
new file mode 100644
index 0000000..4470754
--- /dev/null
+++ b/core/res/res/color/background_cache_hint_selector_device_default.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_accelerated="false" android:color="?attr/colorBackground" />
+ <item android:color="@android:color/transparent" />
+</selector>
+
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
index 39d2c95f..3a9031e 100644
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
index 3a9031e..39d2c95f 100644
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png
index b9c364c..217ea4e 100644
--- a/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png
+++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png
index 217ea4e..b9c364c 100644
--- a/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png
+++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png
Binary files differ
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/drawable-xhdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-xhdpi/stat_sys_upload_anim1.png
index cd0ca73..e443f45 100644
--- a/core/res/res/drawable-xhdpi/stat_sys_upload_anim1.png
+++ b/core/res/res/drawable-xhdpi/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-xhdpi/stat_sys_upload_anim2.png
index e443f45..cd0ca73 100644
--- a/core/res/res/drawable-xhdpi/stat_sys_upload_anim2.png
+++ b/core/res/res/drawable-xhdpi/stat_sys_upload_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-xxhdpi/stat_sys_upload_anim1.png
index 39dd3b8..b828430 100644
--- a/core/res/res/drawable-xxhdpi/stat_sys_upload_anim1.png
+++ b/core/res/res/drawable-xxhdpi/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-xxhdpi/stat_sys_upload_anim2.png
index b828430..39dd3b8 100644
--- a/core/res/res/drawable-xxhdpi/stat_sys_upload_anim2.png
+++ b/core/res/res/drawable-xxhdpi/stat_sys_upload_anim2.png
Binary files differ
diff --git a/core/res/res/layout-notround-watch/alert_dialog_title_material.xml b/core/res/res/layout-notround-watch/alert_dialog_title_material.xml
index 307c6db..0ab56f9 100644
--- a/core/res/res/layout-notround-watch/alert_dialog_title_material.xml
+++ b/core/res/res/layout-notround-watch/alert_dialog_title_material.xml
@@ -22,6 +22,7 @@
android:gravity="top|center_horizontal"
android:minHeight="@dimen/alert_dialog_title_height">
<ImageView android:id="@+id/icon"
+ android:adjustViewBounds="true"
android:maxHeight="24dp"
android:maxWidth="24dp"
android:layout_marginTop="8dp"
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 0279911..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,23 +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:maxHeight="24dp"
- android:maxWidth="24dp"
- android:layout_marginTop="12dp"
- android:layout_width="wrap_content"
+ android:orientation="vertical"
+ android:gravity="top|center_horizontal">
+ <FrameLayout
+ android:adjustViewBounds="true"
+ 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-af/strings.xml b/core/res/res/values-af/strings.xml
index f588520..5ca9f43 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Foonopsies"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Skermslot"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Sit af"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Noodgeval"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Foutverslag"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Neem foutverslag"</string>
<string name="bugreport_message" msgid="398447048750350456">"Dit sal inligting oor die huidige toestand van jou toestel insamel om as \'n e-posboodskap te stuur. Dit sal \'n tydjie neem vandat die foutverslag begin is totdat dit reg is om gestuur te word; wees asseblief geduldig."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g>-USB-datastokkie"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-berging"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Redigeer"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Dataverbruik-waarskuwing"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Datagebruik-opletberig"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tik om gebruik en instellings te bekyk."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G-datalimiet bereik"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G-datalimiet bereik"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index ac2e76b..18fc6dd 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"የ<xliff:g id="MANUFACTURER">%s</xliff:g> ዩኤስቢ አንጻፊ"</string>
<string name="storage_usb" msgid="3017954059538517278">"የUSB ማከማቻ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"አርትዕ"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"የውሂብ አጠቃቀም ማስጠንቀቂየ"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"የውሂብ አጠቃቀም ማንቂያ"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"አጠቃቀምን እና ቅንብሮችን ለማየት መታ ያድርጉ።"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"የ2ጂ-3ጂ ውሂብ ገደብ ላይ ተደርሷል"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"የ4ጂ ውሂብ ገደብ ላይ ተደርሷል"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 3805612..3451bcc 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -222,8 +222,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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1434,7 +1433,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"محرك أقراص USB من <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"وحدة تخزين USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"تعديل"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"تحذير استخدام البيانات"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"تنبيه استخدام البيانات"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"انقر لعرض الاستخدام والإعدادات."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"تم بلوغ حد بيانات اتصال 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"تم بلوغ حد بيانات اتصال 4G"</string>
diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml
index d3ee4a4..1f0d164 100644
--- a/core/res/res/values-az-rAZ/strings.xml
+++ b/core/res/res/values-az-rAZ/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefon seçimləri"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekran kilidi"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Söndür"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Təcili"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Baq hesabatı"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Baqı xəbər verin"</string>
<string name="bugreport_message" msgid="398447048750350456">"Bu, sizin hazırkı cihaz durumu haqqında məlumat toplayacaq ki, elektron məktub şəklində göndərsin. Baq raportuna başlamaq üçün bir az vaxt lazım ola bilər, bir az səbr edin."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB drayv"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB yaddaş"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Düzəliş edin"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Data istifadə xəbərdarlığı"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Data istifadə siqnalı"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"İstifadə və ayarları görmək üçün tıklayın."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G data limitinə çatdı"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G data limitinə çatdı"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 72671f1..2509eae 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -152,7 +152,7 @@
<string name="httpErrorAuth" msgid="1435065629438044534">"Nije moguće potvrditi autentičnost."</string>
<string name="httpErrorProxyAuth" msgid="1788207010559081331">"Potvrda identiteta preko proksi servera nije uspela."</string>
<string name="httpErrorConnect" msgid="8714273236364640549">"Nije moguće povezati se sa serverom."</string>
- <string name="httpErrorIO" msgid="2340558197489302188">"Nije moguće komunicirati sa serverom. Pokušajte ponovo kasnije."</string>
+ <string name="httpErrorIO" msgid="2340558197489302188">"Nije moguće komunicirati sa serverom. Probajte ponovo kasnije."</string>
<string name="httpErrorTimeout" msgid="4743403703762883954">"Veza sa serverom je istekla."</string>
<string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Stranica sadrži previše veza za preusmeravanje sa servera."</string>
<string name="httpErrorUnsupportedScheme" msgid="5015730812906192208">"Protokol nije podržan."</string>
@@ -160,7 +160,7 @@
<string name="httpErrorBadUrl" msgid="3636929722728881972">"Stranicu nije moguće otvoriti zato što je URL adresa nevažeća."</string>
<string name="httpErrorFile" msgid="2170788515052558676">"Nije moguće pristupiti datoteci."</string>
<string name="httpErrorFileNotFound" msgid="6203856612042655084">"Nije moguće pronaći traženu datoteku."</string>
- <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Previše zahteva se obrađuje. Pokušajte ponovo kasnije."</string>
+ <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Previše zahteva se obrađuje. Probajte ponovo kasnije."</string>
<string name="notification_title" msgid="8967710025036163822">"Greška pri prijavljivanju za <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="8353523060269335667">"Sinhronizacija"</string>
<string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sinhronizacija"</string>
@@ -216,8 +216,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opcije telefona"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zaključaj ekran"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Isključi"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Hitni poziv"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Izveštaj o grešci"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Napravi izveštaj o grešci"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ovim će se prikupiti informacije o trenutnom stanju uređaja kako bi bile poslate u poruci e-pošte. Od započinjanja izveštaja o grešci do trenutka za njegovo slanje proći će neko vreme; budite strpljivi."</string>
@@ -441,19 +440,19 @@
<string name="permdesc_manageFingerprint" msgid="178208705828055464">"Dozvoljava aplikaciji da aktivira metode za dodavanje i brisanje šablona otisaka prstiju koji će se koristiti."</string>
<string name="permlab_useFingerprint" msgid="3150478619915124905">"koristi hardver za otiske prstiju"</string>
<string name="permdesc_useFingerprint" msgid="9165097460730684114">"Dozvoljava aplikaciji da koristi hardver za otiske prstiju radi potvrde autentičnosti"</string>
- <string name="fingerprint_acquired_partial" msgid="735082772341716043">"Otkriven je delimični otisak prsta. Pokušajte ponovo."</string>
- <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Nije uspela obrada otiska prsta. Pokušajte ponovo."</string>
+ <string name="fingerprint_acquired_partial" msgid="735082772341716043">"Otkriven je delimični otisak prsta. Probajte ponovo."</string>
+ <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Nije uspela obrada otiska prsta. Probajte ponovo."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"Senzor za otiske prstiju je prljav. Očistite ga i pokušajte ponovo."</string>
- <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Prebrzo ste pomerili prst. Pokušajte ponovo."</string>
- <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"Previše sporo ste pomerili prst. Pokušajte ponovo."</string>
+ <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Prebrzo ste pomerili prst. Probajte ponovo."</string>
+ <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"Previše sporo ste pomerili prst. Probajte ponovo."</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
<string name="fingerprint_error_hw_not_available" msgid="7955921658939936596">"Hardver za otiske prstiju nije dostupan."</string>
<string name="fingerprint_error_no_space" msgid="1055819001126053318">"Nije moguće sačuvati otisak prsta. Uklonite neki od postojećih otisaka prstiju."</string>
- <string name="fingerprint_error_timeout" msgid="3927186043737732875">"Vremensko ograničenje za otisak prsta je isteklo. Pokušajte ponovo."</string>
+ <string name="fingerprint_error_timeout" msgid="3927186043737732875">"Vremensko ograničenje za otisak prsta je isteklo. Probajte ponovo."</string>
<string name="fingerprint_error_canceled" msgid="4402024612660774395">"Radnja sa otiskom prsta je otkazana."</string>
- <string name="fingerprint_error_lockout" msgid="5536934748136933450">"Previše pokušaja. Pokušajte ponovo kasnije."</string>
- <string name="fingerprint_error_unable_to_process" msgid="6107816084103552441">"Pokušajte ponovo."</string>
+ <string name="fingerprint_error_lockout" msgid="5536934748136933450">"Previše pokušaja. Probajte ponovo kasnije."</string>
+ <string name="fingerprint_error_unable_to_process" msgid="6107816084103552441">"Probajte ponovo."</string>
<string name="fingerprint_name_template" msgid="5870957565512716938">"Prst <xliff:g id="FINGERID">%d</xliff:g>"</string>
<string-array name="fingerprint_error_vendor">
</string-array>
@@ -681,8 +680,8 @@
<string name="lockscreen_emergency_call" msgid="5298642613417801888">"Hitne službe"</string>
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Nazad na poziv"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Tačno!"</string>
- <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Pokušajte ponovo"</string>
- <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Pokušajte ponovo"</string>
+ <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Probajte ponovo"</string>
+ <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Probajte ponovo"</string>
<string name="lockscreen_storage_locked" msgid="9167551160010625200">"Otključaj za sve funkcije i podatke"</string>
<string name="faceunlock_multiple_failures" msgid="754137583022792429">"Premašen je najveći dozvoljeni broj pokušaja Otključavanja licem"</string>
<string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"Nema SIM kartice"</string>
@@ -706,19 +705,19 @@
<string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"Pogledajte Korisnički vodič ili kontaktirajte Korisničku podršku."</string>
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM kartica je zaključana."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Otključavanje SIM kartice…"</string>
- <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste nepravilno nacrtali šablon za otključavanje. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste pogrešno uneli lozinku. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste pogrešno uneli PIN. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste netačno uneli šablon za otključavanje. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> nesupešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću podataka za prijavljivanje na Google.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"Neispravno ste nacrtali šablon za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja od vas će biti zatraženo da otključate TV pomoću podataka za prijavljivanje na Google.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="2590227559763762751">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste netačno uneli šablon za otključavanje. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> nesupešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću podataka za prijavljivanje na Google.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
+ <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste nepravilno nacrtali šablon za otključavanje. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste pogrešno uneli lozinku. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste pogrešno uneli PIN. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste netačno uneli šablon za otključavanje. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> nesupešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću podataka za prijavljivanje na Google.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"Neispravno ste nacrtali šablon za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja od vas će biti zatraženo da otključate TV pomoću podataka za prijavljivanje na Google.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="2590227559763762751">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste netačno uneli šablon za otključavanje. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> nesupešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću podataka za prijavljivanje na Google.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="6128106399745755604">"Nepravilno ste pokušali da otključate tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još neuspešnih pokušaja (<xliff:g id="NUMBER_1">%2$d</xliff:g>) tablet će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="950408382418270260">"Pokušali ste da otključate TV netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja TV će biti resetovan na podrazumevana fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="8603565142156826565">"Neispravno ste pokušali da otključate telefon <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još neuspešnih pokušaja (<xliff:g id="NUMBER_1">%2$d</xliff:g>) telefon će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="280873516493934365">"Neispravno ste pokušali da otključate tablet <xliff:g id="NUMBER">%d</xliff:g> puta. Tablet će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="3195755534096192191">"Pokušali ste da otključate TV netačno <xliff:g id="NUMBER">%d</xliff:g> puta. TV će sada biti resetovan na podrazumevana fabrička podešavanja."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="3025504721764922246">"Neispravno ste pokušali da otključate telefon <xliff:g id="NUMBER">%d</xliff:g> puta. Telefon će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
- <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekunde(i)."</string>
+ <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Probajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekunde(i)."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Zaboravili ste šablon?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Otključavanje naloga"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"Previše pokušaja unosa šablona"</string>
@@ -1356,7 +1355,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB disk"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB memorija"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Izmeni"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Upozorenje o potrošnji podataka"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Obaveštenje o potrošnji podataka"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Dodirnite za potrošnju i podešavanja."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Nema više 2G-3G podataka"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Nema više 4G podataka"</string>
@@ -1422,7 +1421,7 @@
<string name="kg_wrong_pattern" msgid="1850806070801358830">"Pogrešan šablon"</string>
<string name="kg_wrong_password" msgid="2333281762128113157">"Pogrešna lozinka"</string>
<string name="kg_wrong_pin" msgid="1131306510833563801">"Pogrešan PIN"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Pokušajte ponovo za <xliff:g id="NUMBER">%1$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Probajte ponovo za <xliff:g id="NUMBER">%1$d</xliff:g> sekunde(i)."</string>
<string name="kg_pattern_instructions" msgid="398978611683075868">"Nacrtajte šablon"</string>
<string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Unesite PIN SIM kartice"</string>
<string name="kg_pin_instructions" msgid="2377242233495111557">"Unesite PIN"</string>
@@ -1444,18 +1443,18 @@
<string name="kg_login_invalid_input" msgid="5754664119319872197">"Nevažeće korisničko ime ili lozinka."</string>
<string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"Zaboravili ste korisničko ime ili lozinku?\nPosetite adresu "<b>"google.com/accounts/recovery"</b>"."</string>
<string name="kg_login_checking_password" msgid="1052685197710252395">"Provera naloga…"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Uneli ste netačni PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Uneli ste netačnu lozinku <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Uneli ste netačni PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Uneli ste netačnu lozinku <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1575557200627128949">"Pokušali ste da otključate tablet netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja tablet će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="5621231220154419413">"Pokušali ste da otključate TV netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja TV će biti resetovan na podrazumevana fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="4051015943038199910">"Pokušali ste da otključate telefon netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja telefon će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2072996269148483637">"Pokušali ste da otključate tablet netačno <xliff:g id="NUMBER">%d</xliff:g> puta. Tablet će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
<string name="kg_failed_attempts_now_wiping" product="tv" msgid="4987878286750741463">"Pokušali ste da otključate TV netačno <xliff:g id="NUMBER">%d</xliff:g> puta. TV će sada biti resetovan na podrazumevana fabrička podešavanja."</string>
<string name="kg_failed_attempts_now_wiping" product="default" msgid="4817627474419471518">"Pokušali ste da otključate telefon netačno <xliff:g id="NUMBER">%d</xliff:g> puta. Telefon će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću naloga e-pošte.\n\nPokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"Neispravno ste nacrtali šablon za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate TV pomoću naloga e-pošte.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću naloga e-pošte.\n\nPokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"Neispravno ste nacrtali šablon za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate TV pomoću naloga e-pošte.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
<string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Ukloni"</string>
<string name="safe_media_volume_warning" product="default" msgid="2276318909314492312">"Želite da pojačate zvuk iznad preporučenog nivoa?\n\nSlušanje glasne muzike duže vreme može da vam ošteti sluh."</string>
@@ -1566,14 +1565,14 @@
<string name="restr_pin_enter_new_pin" msgid="5959606691619959184">"Novi PIN"</string>
<string name="restr_pin_confirm_pin" msgid="8501523829633146239">"Potvrdite novi PIN"</string>
<string name="restr_pin_create_pin" msgid="8017600000263450337">"Napravite PIN za izmenu ograničenja"</string>
- <string name="restr_pin_error_doesnt_match" msgid="2224214190906994548">"PIN-ovi se ne podudaraju. Pokušajte ponovo."</string>
+ <string name="restr_pin_error_doesnt_match" msgid="2224214190906994548">"PIN-ovi se ne podudaraju. Probajte ponovo."</string>
<string name="restr_pin_error_too_short" msgid="8173982756265777792">"PIN je prekratak. Mora da sadrži najmanje 4 cifre."</string>
<plurals name="restr_pin_countdown" formatted="false" msgid="9061246974881224688">
- <item quantity="one">Pokušajte ponovo za <xliff:g id="COUNT">%d</xliff:g> sekundu</item>
- <item quantity="few">Pokušajte ponovo za <xliff:g id="COUNT">%d</xliff:g> sekunde</item>
- <item quantity="other">Pokušajte ponovo za <xliff:g id="COUNT">%d</xliff:g> sekundi</item>
+ <item quantity="one">Probajte ponovo za <xliff:g id="COUNT">%d</xliff:g> sekundu</item>
+ <item quantity="few">Probajte ponovo za <xliff:g id="COUNT">%d</xliff:g> sekunde</item>
+ <item quantity="other">Probajte ponovo za <xliff:g id="COUNT">%d</xliff:g> sekundi</item>
</plurals>
- <string name="restr_pin_try_later" msgid="973144472490532377">"Pokušajte ponovo kasnije"</string>
+ <string name="restr_pin_try_later" msgid="973144472490532377">"Probajte ponovo kasnije"</string>
<string name="immersive_cling_title" msgid="8394201622932303336">"Prikazuje se ceo ekran"</string>
<string name="immersive_cling_description" msgid="3482371193207536040">"Da biste izašli, prevucite nadole odozgo."</string>
<string name="immersive_cling_positive" msgid="5016839404568297683">"Važi"</string>
diff --git a/core/res/res/values-be-rBY/strings.xml b/core/res/res/values-be-rBY/strings.xml
index 3f2e07c..f5f1e62 100644
--- a/core/res/res/values-be-rBY/strings.xml
+++ b/core/res/res/values-be-rBY/strings.xml
@@ -218,8 +218,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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"SOS-выклік"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Справаздача пра памылкі"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Справаздача пра памылку"</string>
<string name="bugreport_message" msgid="398447048750350456">"Будзе збiрацца iнфармацыя пра бягучы стан прылады, якая будзе адпраўляцца на электронную пошту. Стварэнне справаздачы пра памылкi зойме некаторы час."</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-дыск <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-назапашвальнік"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Рэдагаваць"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Папярэджанне выкарыстання дадзеных"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Абвестка аб выкарыстанні трафіка"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Прагляд выкарыстання і налад."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Дасягнуты ліміт трафіку 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Дасягнуты ліміт трафіку 4G"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index e7ac345..f6eb810 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB устройство от <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB хранилище"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Редактиране"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Предупрежд. за ползване на данни"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Сигнал за преноса на данни"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Пренос и настройки: Докоснете за преглед."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Достигнат лимит за 2G/3G данните"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Достигнат лимит за 4G данните"</string>
diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml
index b69a123..f873b1c 100644
--- a/core/res/res/values-bn-rBD/strings.xml
+++ b/core/res/res/values-bn-rBD/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ড্রাইভ"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB সঞ্চয়স্থান"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"সম্পাদনা করুন"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ডেটা ব্যবহারের সতর্কতা"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ডেটা ব্যবহারের সতর্কতা"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"ব্যবহার এবং সেটিংস দেখতে আলতো চাপুন৷"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ডেটা সীমা ছাড়িয়েছে"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ডেটা সীমা ছাড়িয়েছে"</string>
diff --git a/core/res/res/values-bs-rBA/strings.xml b/core/res/res/values-bs-rBA/strings.xml
index 019ff1d..7cb46fe 100644
--- a/core/res/res/values-bs-rBA/strings.xml
+++ b/core/res/res/values-bs-rBA/strings.xml
@@ -216,8 +216,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opcije telefona"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zaključavanje ekrana"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Isključi telefon"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Hitni slučaj"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Izvještaj o greškama"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Kreirajte izvještaj o greškama"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ovim će se prikupljati informacije o trenutnom stanju uređaja, koji će biti poslani kao poruka e-pošte. Može malo potrajati dok se izvještaj o greškama ne kreira i bude spreman za slanje. Budite strpljivi."</string>
@@ -1358,7 +1357,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB disk"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB pohrana"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Uredi"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Upozorenje za prijenos podataka"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Upozorenje o prijenosu podataka"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Dodirnite za prikaz upotrebe i postavki."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Dostignut limit za 2G-3G podatke"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Dostignut limit za 4G podatke"</string>
@@ -1599,7 +1598,7 @@
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ažurirao administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Izbrisao administrator"</string>
<string name="battery_saver_description" msgid="1960431123816253034">"Da bi se trajanje baterije produžilo, opcija za štednju baterije minimizira rad uređaja i ograničava vibriranje, usluge lokacije i većinu prijenosa podataka u pozadini. E-pošta, poruke i druge aplikacije koje se oslanjaju na sinhronizaciju ne mogu biti ažurirane dok ih ne otvorite.\n\nŠtednja baterije se automatski isključi prilikom punjenja uređaja."</string>
- <string name="data_saver_description" msgid="6015391409098303235">"Da bi se smanjilo korištenje podataka, usluga Ušteda podataka sprečava da neke aplikacije šalju ili primaju podatke u pozadini. Aplikacija koju trenutno koristite može pristupiti podacima, ali se to može desiti rjeđe. To može značiti, naprimjer, da se slike ne prikazuju sve dok ih ne dodirnete."</string>
+ <string name="data_saver_description" msgid="6015391409098303235">"Da bi se smanjio prijenos podataka, usluga Ušteda podataka sprečava da neke aplikacije šalju ili primaju podatke u pozadini. Aplikacija koju trenutno koristite može pristupiti podacima, ali se to može desiti rjeđe. To može značiti, naprimjer, da se slike ne prikazuju sve dok ih ne dodirnete."</string>
<string name="data_saver_enable_title" msgid="4674073932722787417">"Uključiti Uštedu podataka?"</string>
<string name="data_saver_enable_button" msgid="7147735965247211818">"Uključi"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 8926fce..d1fbebb 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opcions del telèfon"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueig de pantalla"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Apaga"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergències"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Informe d\'error"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Crea informe d\'errors"</string>
<string name="bugreport_message" msgid="398447048750350456">"Es recopilarà informació sobre l\'estat actual del dispositiu i se t\'enviarà per correu electrònic. Passaran uns quants minuts des de l\'inici de l\'informe d\'errors fins al seu enviament, per la qual cosa et recomanem que tinguis paciència."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unitat USB de: <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Emmagatzematge USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edita"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Advertiment d\'ús de dades"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta d\'ús de dades"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Toca per veure l\'ús i la configuració."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Límit de dades 2G-3G assolit"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Límit de dades 4G assolit"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index a9ff27d..0e6ba82 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -218,8 +218,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Možnosti telefonu"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zámek obrazovky"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Vypnout"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Stav nouze"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Hlášení chyb"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Vytvořit chybové hlášení"</string>
<string name="bugreport_message" msgid="398447048750350456">"Shromažďuje informace o aktuálním stavu zařízení. Tyto informace je následně možné poslat v e-mailové zprávě, chvíli však potrvá, než bude hlášení o chybě připraveno k odeslání. Buďte prosím trpěliví."</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Jednotka USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Úložiště USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Upravit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Upozornění na využití dat"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Upozornění na používání dat"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Klepnutím zobrazíte nastavení."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Dosáhli jste limitu dat 2G–3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Dosáhli jste limitu dat 4G"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 487432b..628f9bb 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Indstillinger for telefon"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Skærmlås"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Sluk"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Nødopkald"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Fejlrapport"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Lav fejlrapport"</string>
<string name="bugreport_message" msgid="398447048750350456">"Der indsamles oplysninger om din enheds aktuelle status, der efterfølgende sendes i en e-mail. Der går lidt tid, fra fejlrapporten påbegyndes, til den er klar til at blive sendt. Tak for tålmodigheden."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-drev fra <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-lager"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Rediger"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Advarsel om dataforbrug"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Underretning om dataforbrug"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tryk for at se forbrug og indstillinger."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Grænsen for 2G-3G-data er nået"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Grænsen for 4G-data er nået"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 6b05253..05155ca 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefonoptionen"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Displaysperre"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Ausschalten"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Notfall"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Fehlerbericht"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Fehlerbericht abrufen"</string>
<string name="bugreport_message" msgid="398447048750350456">"Bei diesem Fehlerbericht werden Daten zum aktuellen Status deines Geräts erfasst und als E-Mail versandt. Vom Start des Berichts bis zu seinem Versand kann es eine Weile dauern. Bitte habe etwas Geduld."</string>
@@ -418,7 +417,7 @@
<string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Ermöglicht der App, Pakete zu empfangen, die mithilfe von Multicast-Adressen an sämtliche Geräte in einem WLAN versendet wurden, nicht nur an dein Telefon. Dies nimmt mehr Leistung in Anspruch als der Nicht-Multicast-Modus."</string>
<string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"Auf Bluetooth-Einstellungen zugreifen"</string>
<string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Ermöglicht der App, das lokale Bluetooth-Tablet zu konfigurieren, Remote-Geräte zu erkennen und eine Verbindung zu diesen herzustellen"</string>
- <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Ermöglicht der App, den lokalen Bluetooth-Fernseher zu konfigurieren, Remote-Geräte zu erkennen und ein Pairing mit diesen durchzuführen"</string>
+ <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Ermöglicht der App, den lokalen Bluetooth-Fernseher zu konfigurieren, Remote-Geräte zu erkennen und eine Kopplung mit ihnen durchzuführen"</string>
<string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Ermöglicht der App, das lokale Bluetooth-Telefon zu konfigurieren, Remote-Geräte zu erkennen und eine Verbindung zu diesen herzustellen"</string>
<string name="permlab_accessWimaxState" msgid="4195907010610205703">"WiMAX-Verbindungen herstellen und trennen"</string>
<string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Ermöglicht der App festzustellen, ob WiMAX aktiviert ist. Zudem kann sie Informationen zu verbundenen WiMAX-Netzwerken abrufen."</string>
@@ -426,9 +425,9 @@
<string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Ermöglicht der App, eine Verbindung zwischen dem Tablet und WiMAX-Netzwerken herzustellen und solche zu trennen."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"Ermöglicht der App, eine Verbindung zwischen dem Fernseher und WiMAX-Netzwerken herzustellen und diese zu trennen"</string>
<string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Ermöglicht der App, eine Verbindung zwischen dem Telefon und WiMAX-Netzwerken herzustellen und solche zu trennen."</string>
- <string name="permlab_bluetooth" msgid="6127769336339276828">"Pairing mit Bluetooth-Geräten durchführen"</string>
+ <string name="permlab_bluetooth" msgid="6127769336339276828">"Kopplung mit Bluetooth-Geräten durchführen"</string>
<string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Ermöglicht der App, die Bluetooth-Konfiguration eines Tablets einzusehen und Verbindungen zu gekoppelten Geräten herzustellen und zu akzeptieren."</string>
- <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Ermöglicht der App, die Bluetooth-Konfiguration des Fernsehers einzusehen und Verbindungen zu Pairing-Geräten herzustellen und zu akzeptieren"</string>
+ <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Ermöglicht der App, die Bluetooth-Konfiguration des Fernsehers abzurufen und Verbindungen zu gekoppelten Geräten herzustellen und zu akzeptieren"</string>
<string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Ermöglicht der App, die Bluetooth-Konfiguration des Telefons einzusehen und Verbindungen mit gekoppelten Geräten herzustellen und zu akzeptieren."</string>
<string name="permlab_nfc" msgid="4423351274757876953">"Nahfeldkommunikation steuern"</string>
<string name="permdesc_nfc" msgid="7120611819401789907">"Ermöglicht der App die Kommunikation mit Tags für die Nahfeldkommunikation, Karten und Readern"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-Speichergerät von <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-Speicher"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Bearbeiten"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Warnung zum Datenverbrauch"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Warnung zur Datennutzung"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Für Nutzung und Einstellungen tippen."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-/3G-Datenlimit erreicht"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G-Datenlimit erreicht"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index a6ee8c2..4283827 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -251,7 +250,7 @@
<string name="permgrouplab_calendar" msgid="5863508437783683902">"Ημερολόγιο"</string>
<string name="permgroupdesc_calendar" msgid="3889615280211184106">"έχει πρόσβαση στο ημερολόγιό σας"</string>
<string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</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>
@@ -274,149 +273,149 @@
<string name="capability_desc_canControlMagnification" msgid="4791858203568383773">"Ελέγξτε το επίπεδο ζουμ και τη θέση της οθόνης."</string>
<string name="capability_title_canPerformGestures" msgid="7418984730362576862">"Εκτέλεση κινήσεων"</string>
<string name="capability_desc_canPerformGestures" msgid="8296373021636981249">"Επιτρέπει το πάτημα, την ολίσθηση, το πλησίασμα και άλλες κινήσεις."</string>
- <string name="permlab_statusBar" msgid="7417192629601890791">"απενεργοποίηση ή τροποποίηση γραμμής κατάστασης"</string>
+ <string name="permlab_statusBar" msgid="7417192629601890791">"απενεργοποιεί ή να τροποποιεί την γραμμή κατάστασης"</string>
<string name="permdesc_statusBar" msgid="8434669549504290975">"Επιτρέπει στην εφαρμογή να απενεργοποιεί τη γραμμή κατάστασης ή να προσθέτει και να αφαιρεί εικονίδια συστήματος."</string>
- <string name="permlab_statusBarService" msgid="4826835508226139688">"ορισμός ως γραμμής κατάστασης"</string>
+ <string name="permlab_statusBarService" msgid="4826835508226139688">"ορίζεται ως γραμμή κατάστασης"</string>
<string name="permdesc_statusBarService" msgid="716113660795976060">"Επιτρέπει στην εφαρμογή να αποτελεί τη γραμμή κατάστασης."</string>
- <string name="permlab_expandStatusBar" msgid="1148198785937489264">"ανάπτυξη/σύμπτυξη γραμμής κατάστασης"</string>
+ <string name="permlab_expandStatusBar" msgid="1148198785937489264">"αναπτύσσει/συμπτύσσει τη γραμμή κατάστασης"</string>
<string name="permdesc_expandStatusBar" msgid="6917549437129401132">"Επιτρέπει στην εφαρμογή να αναπτύξει ή να συμπτύξει τη γραμμή κατάστασης."</string>
- <string name="permlab_install_shortcut" msgid="4279070216371564234">"εγκατάσταση συντομεύσεων"</string>
+ <string name="permlab_install_shortcut" msgid="4279070216371564234">"εγκαθιστά συντομεύσεις"</string>
<string name="permdesc_install_shortcut" msgid="8341295916286736996">"Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων στην Αρχική οθόνη χωρίς την παρέμβαση του χρήστη."</string>
- <string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"κατάργηση εγκατάστασης συντομεύσεων"</string>
+ <string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"καταργεί την εγκατάσταση συντομεύσεων"</string>
<string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Επιτρέπει στην εφαρμογή την κατάργηση συντομεύσεων από την Αρχική οθόνη χωρίς την παρέμβαση του χρήστη."</string>
- <string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"αναδρομολόγηση εξερχόμενων κλήσεων"</string>
+ <string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"αναδρομολογεί τις εξερχόμενες κλήσεις"</string>
<string name="permdesc_processOutgoingCalls" msgid="5156385005547315876">"Επιτρέπει στην εφαρμογή να βλέπει τον αριθμό που καλέσατε κατά τη διάρκεια μιας εξερχόμενης κλήσης με επιλογή ανακατεύθυνσης της κλήσης σε έναν διαφορετικό αριθμό ή διακοπής της κλήσης."</string>
- <string name="permlab_receiveSms" msgid="8673471768947895082">"λήψη μηνυμάτων κειμένου (SMS)"</string>
+ <string name="permlab_receiveSms" msgid="8673471768947895082">"λαμβάνει μηνύματα κειμένου (SMS)"</string>
<string name="permdesc_receiveSms" msgid="6424387754228766939">"Επιτρέπει στην εφαρμογή τη λήψη και την επεξεργασία μηνυμάτων SMS. Αυτό σημαίνει ότι η εφαρμογή θα μπορούσε να παρακολουθήσει ή να διαγράψει τα μηνύματα που αποστέλλονται στη συσκευή σας χωρίς αυτά να εμφανιστούν σε εσάς."</string>
- <string name="permlab_receiveMms" msgid="1821317344668257098">"λήψη μηνυμάτων κειμένου (MMS)"</string>
+ <string name="permlab_receiveMms" msgid="1821317344668257098">"λαμβάνει μηνύματα κειμένου (MMS)"</string>
<string name="permdesc_receiveMms" msgid="533019437263212260">"Επιτρέπει στην εφαρμογή τη λήψη και την επεξεργασία μηνυμάτων MMS. Αυτό σημαίνει ότι η εφαρμογή θα μπορούσε να παρακολουθήσει ή να διαγράψει τα μηνύματα που αποστέλλονται στη συσκευή σας χωρίς αυτά να εμφανιστούν σε εσάς."</string>
- <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"ανάγνωση μηνυμάτων που έχουν μεταδοθεί μέσω κινητού τηλεφώνου"</string>
+ <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"διαβάζει μηνύματα που έχουν μεταδοθεί μέσω κινητού τηλεφώνου"</string>
<string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Επιτρέπει στην εφαρμογή την ανάγνωση μηνυμάτων που έχουν μεταδοθεί μέσω κινητού τηλεφώνου και έχουν ληφθεί από τη συσκευή σας. Ειδοποιήσεις που μεταδίδονται μέσω κινητού παραδίδονται σε ορισμένες τοποθεσίες για να σας προειδοποιήσουν για καταστάσεις έκτακτης ανάγκης. Κακόβουλες εφαρμογές ενδέχεται να παρεμποδίσουν την απόδοση ή τη λειτουργία της συσκευής σας κατά τη λήψη μετάδοσης μέσω κινητού σχετικά με μια επείγουσα κατάσταση."</string>
- <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"ανάγνωση ροών δεδομένων στις οποίες έχετε εγγραφεί"</string>
+ <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"διαβάζει ροές δεδομένων στις οποίες έχετε εγγραφεί"</string>
<string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Επιτρέπει στην εφαρμογή τη λήψη λεπτομερειών σχετικά με τις τρέχουσες συγχρονισμένες ροές δεδομένων."</string>
- <string name="permlab_sendSms" msgid="7544599214260982981">"πραγματοποιεί αποστολή και προβολή μηνυμάτων SMS"</string>
+ <string name="permlab_sendSms" msgid="7544599214260982981">"στέλνει και να διαβάζει μηνύματα SMS"</string>
<string name="permdesc_sendSms" msgid="7094729298204937667">"Επιτρέπει στην εφαρμογή των αποστολή μηνυμάτων SMS. Αυτό μπορεί να προκαλέσει μη αναμενόμενες χρεώσεις. Οι κακόβουλες εφαρμογές ενδέχεται να σας κοστίσουν χρήματα, αποστέλλοντας μηνύματα χωρίς την έγκρισή σας."</string>
- <string name="permlab_readSms" msgid="8745086572213270480">"ανάγνωση των μηνυμάτων κειμένου σας (SMS ή MMS)"</string>
+ <string name="permlab_readSms" msgid="8745086572213270480">"διαβάζει τα μηνύματα κειμένου (SMS ή MMS)"</string>
<string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Επιτρέπει στην εφαρμογή την ανάγνωση μηνυμάτων SMS που είναι αποθηκευμένα στο tablet σας ή στην κάρτα σας SIM. Αυτό δίνει τη δυνατότητα στην εφαρμογή να διαβάζει όλα τα μηνύματα SMS, ανεξάρτητα από το περιεχόμενο ή το επίπεδο εμπιστευτικότητάς τους."</string>
<string name="permdesc_readSms" product="tv" msgid="5102425513647038535">"Επιτρέπει στην εφαρμογή να διαβάζει μηνύματα SMS που έχουν αποθηκευτεί στην τηλεόραση ή στην κάρτα SIM. Έτσι, η εφαρμογή μπορεί να διαβάζει όλα τα μηνύματα SMS, ανεξαρτήτως περιεχομένου ή εμπιστευτικότητας."</string>
<string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Επιτρέπει στην εφαρμογή την ανάγνωση μηνυμάτων SMS που είναι αποθηκευμένα στο τηλέφωνό σας ή στην κάρτα σας SIM. Αυτό δίνει τη δυνατότητα στην εφαρμογή να διαβάζει όλα τα μηνύματα SMS, ανεξάρτητα από το περιεχόμενο ή το επίπεδο εμπιστευτικότητάς τους."</string>
- <string name="permlab_receiveWapPush" msgid="5991398711936590410">"λήψη μηνυμάτων κειμένου (WAP)"</string>
+ <string name="permlab_receiveWapPush" msgid="5991398711936590410">"λαμβάνει μηνύματα κειμένου (WAP)"</string>
<string name="permdesc_receiveWapPush" msgid="748232190220583385">"Επιτρέπει στην εφαρμογή τη λήψη και την επεξεργασία μηνυμάτων WAP. Αυτό σημαίνει ότι η εφαρμογή θα μπορούσε να παρακολουθήσει ή να διαγράψει τα μηνύματα που αποστέλλονται στη συσκευή σας χωρίς αυτά να εμφανιστούν σε εσάς."</string>
- <string name="permlab_getTasks" msgid="6466095396623933906">"ανάκτηση εκτελούμενων εφαρμογών"</string>
+ <string name="permlab_getTasks" msgid="6466095396623933906">"ανακτά εκτελούμενες εφαρμογές"</string>
<string name="permdesc_getTasks" msgid="7454215995847658102">"Επιτρέπει στην εφαρμογή την ανάκτηση πληροφοριών σχετικά με τρέχουσες και πρόσφατα εκτελούμενες εργασίες. Αυτό μπορεί να δίνει τη δυνατότητα στην εφαρμογή να ανακαλύπτει πληροφορίες σχετικά με το ποιες εφαρμογές χρησιμοποιούνται στη συσκευή."</string>
- <string name="permlab_manageProfileAndDeviceOwners" msgid="7918181259098220004">"διαχείριση προφίλ και κατόχων συσκευής"</string>
+ <string name="permlab_manageProfileAndDeviceOwners" msgid="7918181259098220004">"διαχειρίζεται το προφίλ και τους κατόχους συσκευής"</string>
<string name="permdesc_manageProfileAndDeviceOwners" msgid="106894851498657169">"Επιτρέπει σε εφαρμογές να ορίζουν τους κατόχους προφίλ και τον κάτοχο της συσκευής."</string>
- <string name="permlab_reorderTasks" msgid="2018575526934422779">"αναδιάταξη εκτελούμενων εφαρμογών"</string>
+ <string name="permlab_reorderTasks" msgid="2018575526934422779">"αναδιατάσσει τις εκτελούμενες εφαρμογές"</string>
<string name="permdesc_reorderTasks" msgid="7734217754877439351">"Επιτρέπει στην εφαρμογή τη μετακίνηση εργασιών στο προσκήνιο και το παρασκήνιο. Η εφαρμογή μπορεί να το κάνει αυτό χωρίς να καταχωρίσετε δεδομένα εισόδου."</string>
- <string name="permlab_enableCarMode" msgid="5684504058192921098">"ενεργοποίηση λειτουργίας αυτοκινήτου"</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"ενεργοποιεί την λειτουργία αυτοκινήτου"</string>
<string name="permdesc_enableCarMode" msgid="4853187425751419467">"Επιτρέπει στην εφαρμογή την ενεργοποίηση της λειτουργίας αυτοκινήτου."</string>
- <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"κλείσιμο των άλλων εφαρμογών"</string>
+ <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"κλείνει άλλες εφαρμογές"</string>
<string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"Επιτρέπει στην εφαρμογή τον τερματισμό των διεργασιών παρασκηνίου άλλων εφαρμογών. Αυτό μπορεί να προκαλεί τη διακοπή λειτουργίας άλλων εφαρμογών."</string>
- <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"σχεδίαση πάνω σε άλλες εφαρμογές"</string>
+ <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"σχεδιάζει πάνω από άλλες εφαρμογές"</string>
<string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Επιτρέπει στην εφαρμογή το σχεδιασμό πάνω σε άλλες εφαρμογές ή τμήματα του περιβάλλοντος χρήστη. Ενδέχεται να παρεμβαίνουν στη χρήση του περιβάλλοντος σε άλλες εφαρμογές ή να αλλάζουν τα στοιχεία που βλέπετε σε άλλες εφαρμογές."</string>
- <string name="permlab_persistentActivity" msgid="8841113627955563938">"να εκτελείται συνεχώς η εφαρμογή"</string>
+ <string name="permlab_persistentActivity" msgid="8841113627955563938">"επιτρέπει στην εφαρμογή να εκτελείται συνεχώς"</string>
<string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"Επιτρέπει στην εφαρμογή να δημιουργεί μόνιμα τμήματα του εαυτού της στη μνήμη. Αυτό μπορεί να περιορίζει τη μνήμη που διατίθεται σε άλλες εφαρμογές, καθυστερώντας τη λειτουργία του tablet."</string>
<string name="permdesc_persistentActivity" product="tv" msgid="5086862529499103587">"Επιτρέπει στην εφαρμογή να καθιστά τμήματά της μόνιμα στη μνήμη. Αυτό μπορεί να περιορίσει τη μνήμη που διατίθεται σε άλλες εφαρμογές, επιβραδύνοντας τη λειτουργία της τηλεόρασης."</string>
<string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"Επιτρέπει στην εφαρμογή να δημιουργεί μόνιμα τμήματα του εαυτού της στη μνήμη. Αυτό μπορεί να περιορίζει τη μνήμη που διατίθεται σε άλλες εφαρμογές, καθυστερώντας τη λειτουργία του τηλεφώνου."</string>
- <string name="permlab_getPackageSize" msgid="7472921768357981986">"μέτρηση αποθηκευτικού χώρου εφαρμογής"</string>
+ <string name="permlab_getPackageSize" msgid="7472921768357981986">"υπολογίζει τον αποθηκευτικό χώρο εφαρμογής"</string>
<string name="permdesc_getPackageSize" msgid="3921068154420738296">"Επιτρέπει στην εφαρμογή να ανακτήσει τα μεγέθη κώδικα, δεδομένων και προσωρινής μνήμης"</string>
<string name="permlab_writeSettings" msgid="2226195290955224730">"τροποποίηση ρυθμίσεων συστήματος"</string>
<string name="permdesc_writeSettings" msgid="7775723441558907181">"Επιτρέπει στην εφαρμογή την τροποποίηση των δεδομένων των ρυθμίσεων του συστήματος. Τυχόν κακόβουλες εφαρμογές ενδέχεται να καταστρέψουν τη διαμόρφωση του συστήματός σας."</string>
- <string name="permlab_receiveBootCompleted" msgid="5312965565987800025">"εκτέλεση κατά την έναρξη"</string>
+ <string name="permlab_receiveBootCompleted" msgid="5312965565987800025">"εκτελείται κατά την έναρξη"</string>
<string name="permdesc_receiveBootCompleted" product="tablet" msgid="7390304664116880704">"Επιτρέπει στην εφαρμογή να εκκινηθεί αμέσως μόλις ολοκληρωθεί η εκκίνηση του συστήματος. Αυτό ενδέχεται να καθυστερήσει την εκκίνηση του tablet και να προκαλέσει γενική μείωση της ταχύτητας λειτουργίας του tablet, καθώς η εφαρμογή θα εκτελείται συνεχώς."</string>
<string name="permdesc_receiveBootCompleted" product="tv" msgid="4525890122209673621">"Επιτρέπει στην εφαρμογή να ξεκινάει μόλις ολοκληρώνεται η εκκίνηση του συστήματος. Αυτό μπορεί να καθυστερεί την εκκίνηση της τηλεόρασης και επιτρέπει στην εφαρμογή να επιβραδύνει τη συνολική λειτουργία του tablet, λόγω της συνεχούς προβολής."</string>
<string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"Επιτρέπει στην εφαρμογή να εκκινηθεί αμέσως μόλις ολοκληρωθεί η εκκίνηση του συστήματος. Αυτό ενδέχεται να καθυστερήσει την εκκίνηση του τηλεφώνου και να προκαλέσει γενική μείωση της ταχύτητας λειτουργίας του τηλεφώνου, καθώς η εφαρμογή θα εκτελείται συνεχώς."</string>
- <string name="permlab_broadcastSticky" msgid="7919126372606881614">"αποστολή εκπομπής sticky"</string>
+ <string name="permlab_broadcastSticky" msgid="7919126372606881614">"στέλνει εκπομπή sticky"</string>
<string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"Επιτρέπει στην εφαρμογή την αποστολή εκπομπών sticky, οι οποίες παραμένουν μετά το τέλος της εκπομπής. Η υπερβολική χρήση ενδέχεται να καταστήσει τη λειτουργία του tablet αργή ή ασταθή, προκαλώντας τη χρήση μεγάλου τμήματος της μνήμης."</string>
<string name="permdesc_broadcastSticky" product="tv" msgid="6839285697565389467">"Επιτρέπει στην εφαρμογή να στέλνει εκπομπές που παραμένουν μετά το τέλος της μετάδοσης. Η υπερβολική χρήση μπορεί να καταστήσει αργή ή ασταθή τη λειτουργία της τηλεόρασης, προκαλώντας τη χρήση υπερβολικά μεγάλου μέρους της μνήμης."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"Επιτρέπει στην εφαρμογή την αποστολή εκπομπών sticky, οι οποίες παραμένουν μετά το τέλος της εκπομπής. Η υπερβολική χρήση ενδέχεται να καταστήσει τη λειτουργία του τηλεφώνου αργή ή ασταθή, προκαλώντας τη χρήση μεγάλου τμήματος της μνήμης."</string>
- <string name="permlab_readContacts" msgid="8348481131899886131">"ανάγνωση των επαφών σας"</string>
+ <string name="permlab_readContacts" msgid="8348481131899886131">"διαβάζει τις επαφές σας"</string>
<string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Επιτρέπει στην εφαρμογή την ανάγνωση δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο tablet σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένα άτομα ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας και οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
<string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"Επιτρέπει στην εφαρμογή να διαβάζει δεδομένα σχετικά με τις επαφές σας που είναι αποθηκευμένες στην τηλεόρασή σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένα άτομα ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτό το δικαίωμα επιτρέπει στις εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας. Επίσης, κακόβουλες εφαρμογές μπορεί να μοιραστούν δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
<string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Επιτρέπει στην εφαρμογή την ανάγνωση δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο τηλέφωνό σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένα άτομα ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας και οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
- <string name="permlab_writeContacts" msgid="5107492086416793544">"τροποποίηση των επαφών σας"</string>
+ <string name="permlab_writeContacts" msgid="5107492086416793544">"τροποποιεί τις επαφές σας"</string>
<string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Επιτρέπει στην εφαρμογή την τροποποίηση των δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο tablet σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένες επαφές ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
<string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"Επιτρέπει στην εφαρμογή να τροποποιεί τα δεδομένα σχετικά με τις επαφές που είναι αποθηκευμένες στην τηλεόρασή σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένες επαφές ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτό το δικαίωμα δίνει τη δυνατότητα σε εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
<string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Επιτρέπει στην εφαρμογή την τροποποίηση των δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο τηλέφωνό σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένες επαφές ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
- <string name="permlab_readCallLog" msgid="3478133184624102739">"ανάγνωση αρχείου καταγραφής κλήσεων"</string>
+ <string name="permlab_readCallLog" msgid="3478133184624102739">"διαβάζει το αρχείο καταγραφής κλήσεων"</string>
<string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"Επιτρέπει στην εφαρμογή την ανάγνωση του αρχείου καταγραφής κλήσεων του tablet σας, συμπεριλαμβανομένων των δεδομένων σχετικά με τις εισερχόμενες και τις εξερχόμενες κλήσεις. Αυτή η άδεια δίνει τη δυνατότητα στις εφαρμογές να αποθηκεύει τα δεδομένα του αρχείου καταγραφής κλήσεων και οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται δεδομένα του αρχείου καταγραφής κλήσεων χωρίς να το γνωρίζετε."</string>
<string name="permdesc_readCallLog" product="tv" msgid="5611770887047387926">"Επιτρέπει στην εφαρμογή να διαβάζει το αρχείο καταγραφής κλήσεων της τηλεόρασής σας, συμπεριλαμβανομένων δεδομένων σχετικά με τις εισερχόμενες και τις εξερχόμενες κλήσεις. Αυτό το δικαίωμα επιτρέπει στις εφαρμογές να αποθηκεύουν τα δεδομένα του αρχείου καταγραφής κλήσεων. Επίσης, κακόβουλες εφαρμογές μπορεί να μοιραστούν δεδομένα αρχείου καταγραφής κλήσεων χωρίς να το γνωρίζετε."</string>
<string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"Επιτρέπει στην εφαρμογή την ανάγνωση του αρχείου καταγραφής κλήσεων του τηλεφώνου σας, συμπεριλαμβανομένων των δεδομένων σχετικά με τις εισερχόμενες και τις εξερχόμενες κλήσεις. Αυτή η άδεια δίνει τη δυνατότητα στις εφαρμογές να αποθηκεύει τα δεδομένα του αρχείου καταγραφής κλήσεων και οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται δεδομένα του αρχείου καταγραφής κλήσεων χωρίς να το γνωρίζετε."</string>
- <string name="permlab_writeCallLog" msgid="8552045664743499354">"εγγραφή αρχείου καταγραφής κλήσεων"</string>
+ <string name="permlab_writeCallLog" msgid="8552045664743499354">"εγγράφει αρχείο καταγραφής κλήσεων"</string>
<string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Επιτρέπει στην εφαρμογή να τροποποιεί το αρχείο καταγραφής κλήσεων του tablet σας, συμπεριλαμβανομένων των δεδομένων σχετικά με τις εισερχόμενες και τις εξερχόμενες κλήσεις. Οι κακόβουλες εφαρμογές μπορεί να το χρησιμοποιήσουν για να διαγράψουν ή να τροποποιήσουν το αρχείο καταγραφής κλήσεων."</string>
<string name="permdesc_writeCallLog" product="tv" msgid="4225034892248398019">"Επιτρέπει στην εφαρμογή να τροποποιεί το αρχείο καταγραφής κλήσεων της τηλεόρασής σας, συμπεριλαμβανομένων των δεδομένων σχετικά με τις εισερχόμενες και τις εξερχόμενες κλήσεις. Κακόβουλες εφαρμογές μπορεί να χρησιμοποιήσουν αυτήν τη δυνατότητα, για να διαγράψουν ή να τροποποιήσουν το αρχείο καταγραφής κλήσεων."</string>
<string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Επιτρέπει στην εφαρμογή να τροποποιεί το αρχείο καταγραφής κλήσεων του τηλεφώνου σας, συμπεριλαμβανομένων των δεδομένων σχετικά με τις εισερχόμενες και τις εξερχόμενες κλήσεις. Οι κακόβουλες εφαρμογές μπορεί να το χρησιμοποιήσουν για να διαγράψουν ή να τροποποιήσουν το αρχείο καταγραφής κλήσεων."</string>
<string name="permlab_bodySensors" msgid="4683341291818520277">"πρόσβαση στους αισθητήρες λειτουργιών (π.χ. παρακολούθηση καρδιακού παλμού)"</string>
<string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"Επιτρέπει στην εφαρμογή να αποκτήσει πρόσβαση στα δεδομένα των αισθητήρων που παρακολουθούν τη φυσική σας κατάσταση, όπως τον καρδιακό ρυθμό σας."</string>
- <string name="permlab_readCalendar" msgid="5972727560257612398">"ανάγνωση συμβάντων ημερολογίου και εμπιστευτικών πληροφοριών"</string>
+ <string name="permlab_readCalendar" msgid="5972727560257612398">"διαβάζει συμβάντα ημερολογίου και εμπιστευτικές πληροφορίες"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="4216462049057658723">"Επιτρέπει στην εφαρμογή την ανάγνωση όλων των συμβάντων ημερολογίου που είναι αποθηκευμένα στο tablet σας, συμπεριλαμβανομένων εκείνων των φίλων ή των συναδέλφων σας. Αυτό μπορεί να δίνει τη δυνατότητα στην εφαρμογή να μοιράζεται ή να αποθηκεύει τα δεδομένα του ημερολογίου σας, ανεξάρτητα από το βαθμό εμπιστευτικότητας ή ευαισθησίας τους."</string>
<string name="permdesc_readCalendar" product="tv" msgid="3191352452242394196">"Επιτρέπει στην εφαρμογή να διαβάζει όλα τα συμβάντα ημερολογίου που είναι αποθηκευμένα στην τηλεόρασή σας, συμπεριλαμβανομένων εκείνων από τους φίλους ή τους συναδέλφους σας. Αυτό μπορεί να επιτρέψει στην εφαρμογή να μοιράζεται ή να αποθηκεύει τα δεδομένα ημερολογίου, ανεξαρτήτως εμπιστευτικότητας ή ευαισθησίας."</string>
<string name="permdesc_readCalendar" product="default" msgid="7434548682470851583">"Επιτρέπει στην εφαρμογή την ανάγνωση όλων των συμβάντων ημερολογίου που είναι αποθηκευμένα στο τηλέφωνό σας, συμπεριλαμβανομένων εκείνων των φίλων ή των συναδέλφων σας. Αυτό μπορεί να δίνει τη δυνατότητα στην εφαρμογή να μοιράζεται ή να αποθηκεύει τα δεδομένα του ημερολογίου σας, ανεξάρτητα από το βαθμό εμπιστευτικότητας ή ευαισθησίας τους."</string>
- <string name="permlab_writeCalendar" msgid="8438874755193825647">"προσθήκη ή τροποποίηση συμβάντων ημερολογίου και αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου σε προσκεκλημένους χωρίς να το γνωρίζουν οι κάτοχοι"</string>
+ <string name="permlab_writeCalendar" msgid="8438874755193825647">"προσθέτει ή τροποποιεί συμβάντα ημερολογίου και να στέλνει μηνύματα ηλ. ταχυδρομείου σε προσκεκλημένους χωρίς να το γνωρίζουν οι κάτοχοι"</string>
<string name="permdesc_writeCalendar" product="tablet" msgid="6679035520113668528">"Επιτρέπει στην εφαρμογή την προσθήκη, την κατάργηση και την αλλαγή συμβάντων που μπορείτε να τροποποιήσετε στο tablet σας, συμπεριλαμβανομένων εκείνων των φίλων ή των συναδέλφων σας. Αυτό μπορεί να επιτρέπει στην εφαρμογή να αποστέλλει μηνύματα που φαίνεται ότι προέρχονται από κατόχους ημερολογίων ή να τροποποιεί συμβάντα χωρίς να το γνωρίζουν οι κάτοχοι."</string>
<string name="permdesc_writeCalendar" product="tv" msgid="1273290605500902507">"Επιτρέπει στην εφαρμογή να προσθέτει, να καταργεί και να αλλάζει συμβάντα που μπορείτε να τροποποιείτε στην τηλεόρασή σας, συμπεριλαμβανομένων όσων ανήκουν σε φίλους ή συναδέλφους. Αυτό ενδέχεται να επιτρέπει στην εφαρμογή να αποστέλλει μηνύματα τα οποία φαίνεται ότι προέρχονται από κατόχους ημερολογίου ή να τροποποιεί συμβάντα χωρίς να το γνωρίζουν οι κάτοχοί τους."</string>
<string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Επιτρέπει στην εφαρμογή την προσθήκη, την κατάργηση και την αλλαγή συμβάντων που μπορείτε να τροποποιήσετε στο τηλέφωνό σας, συμπεριλαμβανομένων εκείνων των φίλων ή των συναδέλφων σας. Αυτό μπορεί να επιτρέπει στην εφαρμογή να αποστέλλει μηνύματα που φαίνεται ότι προέρχονται από κατόχους ημερολογίων ή να τροποποιεί συμβάντα χωρίς να το γνωρίζουν οι κάτοχοι."</string>
- <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"πρόσβαση σε επιπλέον εντολές παρόχου τοποθεσίας"</string>
+ <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"έχει πρόσβαση σε επιπλέον εντολές παρόχου τοποθεσίας"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="6078307221056649927">"Επιτρέπει στην εφαρμογή την πρόσβαση σε επιπλέον εντολές παρόχου τοποθεσίας. Αυτό μπορεί να δώσει τη δυνατότητα στην εφαρμογή να παρέμβει στη λειτουργία του GPS ή άλλων πηγών τοποθεσίας."</string>
- <string name="permlab_accessFineLocation" msgid="251034415460950944">"πρόσβαση στην ακριβή τοποθεσία (με βάση το GPS και το δίκτυο)"</string>
+ <string name="permlab_accessFineLocation" msgid="251034415460950944">"έχει πρόσβαση στην ακριβή τοποθεσία (με βάση το GPS και το δίκτυο)"</string>
<string name="permdesc_accessFineLocation" msgid="5295047563564981250">"Επιτρέπει στην εφαρμογή να λαμβάνει την ακριβή θέση σας με τη χρήση του Παγκόσμιου Συστήματος Εντοπισμού (GPS) ή πηγών τοποθεσίας δικτύου, όπως κεραίες κινητής τηλεφωνίας και Wi-Fi. Αυτές οι υπηρεσίες τοποθεσίας πρέπει να είναι ενεργοποιημένες και διαθέσιμες στην συσκευή σας, ώστε να μπορούν να χρησιμοποιηθούν από την εφαρμογή. Οι εφαρμογές ενδέχεται να τις χρησιμοποιήσουν για να προσδιορίσουν τη θέση σας και ενδέχεται να καταναλώσουν επιπλέον ισχύ μπαταρίας."</string>
- <string name="permlab_accessCoarseLocation" msgid="7715277613928539434">"πρόσβαση στην τοποθεσία κατά προσέγγιση (με βάση το δίκτυο)"</string>
+ <string name="permlab_accessCoarseLocation" msgid="7715277613928539434">"έχει πρόσβαση στην τοποθεσία κατά προσέγγιση (με βάση το δίκτυο)"</string>
<string name="permdesc_accessCoarseLocation" msgid="2538200184373302295">"Επιτρέπει στην εφαρμογή τη λήψη της κατά προσέγγιση τοποθεσίας σας. Αυτή η τοποθεσία προκύπτει από τις υπηρεσίες τοποθεσίας με τη χρήση πηγών τοποθεσίας δικτύου, όπως κεραίες κινητής τηλεφωνίας και Wi-Fi. Αυτές οι υπηρεσίες τοποθεσίας πρέπει να είναι ενεργοποιημένες και διαθέσιμες στην συσκευή σας, ώστε να μπορούν να χρησιμοποιηθούν από την εφαρμογή. Οι εφαρμογές ενδέχεται να τις χρησιμοποιήσουν για να προσδιορίσουν κατά προσέγγιση τη θέση σας."</string>
- <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"αλλαγή των ρυθμίσεων ήχου"</string>
+ <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"αλλάζει τις ρυθμίσεις ήχου"</string>
<string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Επιτρέπει στην εφαρμογή την τροποποίηση καθολικών ρυθμίσεων ήχου, όπως η ένταση και ποιο ηχείο χρησιμοποιείται για έξοδο."</string>
- <string name="permlab_recordAudio" msgid="3876049771427466323">"εγγραφή ήχου"</string>
+ <string name="permlab_recordAudio" msgid="3876049771427466323">"εγγράφει ήχο"</string>
<string name="permdesc_recordAudio" msgid="4906839301087980680">"Επιτρέπει στην εφαρμογή την εγγραφή ήχου με το μικρόφωνο. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να εγγράφει ήχο ανά πάσα στιγμή χωρίς την έγκρισή σας."</string>
<string name="permlab_sim_communication" msgid="2935852302216852065">"στέλνει εντολές στην κάρτα SIM"</string>
<string name="permdesc_sim_communication" msgid="5725159654279639498">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string>
- <string name="permlab_camera" msgid="3616391919559751192">"λήψη φωτογραφιών και βίντεο"</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"κάνει λήψη φωτογραφιών και βίντεο"</string>
<string name="permdesc_camera" msgid="8497216524735535009">"Επιτρέπει στην εφαρμογή τη λήψη φωτογραφιών και βίντεο με τη φωτογραφική μηχανή. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να χρησιμοποιεί τη φωτογραφική μηχανή ανά πάσα στιγμή χωρίς την έγκρισή σας."</string>
- <string name="permlab_vibrate" msgid="7696427026057705834">"έλεγχος δόνησης"</string>
+ <string name="permlab_vibrate" msgid="7696427026057705834">"ελέγχει τη δόνηση"</string>
<string name="permdesc_vibrate" msgid="6284989245902300945">"Επιτρέπει στην εφαρμογή τον έλεγχο της δόνησης."</string>
- <string name="permlab_callPhone" msgid="3925836347681847954">"απευθείας κλήση τηλεφωνικών αριθμών"</string>
+ <string name="permlab_callPhone" msgid="3925836347681847954">"πραγματοποιεί απευθείας κλήση τηλεφωνικών αριθμών"</string>
<string name="permdesc_callPhone" msgid="3740797576113760827">"Επιτρέπει στην εφαρμογή την κλήση αριθμών τηλεφώνου χωρίς δική σας παρέμβαση. Αυτό μπορεί να προκαλέσει μη αναμενόμενες χρεώσεις ή κλήσεις. Έχετε υπόψη ότι δεν επιτρέπεται στην εφαρμογή η κλήση αριθμών έκτακτης ανάγκης. Οι κακόβουλες εφαρμογές ενδέχεται να σας κοστίσουν χρήματα, πραγματοποιώντας κλήσεις χωρίς την έγκρισή σας."</string>
- <string name="permlab_accessImsCallService" msgid="3574943847181793918">"πρόσβαση στην υπηρεσία κλήσεων της IMS"</string>
+ <string name="permlab_accessImsCallService" msgid="3574943847181793918">"έχει πρόσβαση στην υπηρεσία κλήσεων της IMS"</string>
<string name="permdesc_accessImsCallService" msgid="8992884015198298775">"Επιτρέπει στην εφαρμογή τη χρήση της υπηρεσίας IMS για την πραγματοποίηση κλήσεων χωρίς τη δική σας παρέμβαση."</string>
- <string name="permlab_readPhoneState" msgid="9178228524507610486">"ανάγνωση κατάστασης και ταυτότητας τηλεφώνου"</string>
+ <string name="permlab_readPhoneState" msgid="9178228524507610486">"διαβάζει την κατάσταση και ταυτότητα τηλεφώνου"</string>
<string name="permdesc_readPhoneState" msgid="1639212771826125528">"Επιτρέπει στην εφαρμογή την πρόσβαση στις λειτουργίες τηλεφώνου της συσκευής. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να καθορίζει τον αριθμό τηλεφώνου και τα αναγνωριστικά συσκευών, εάν μια κλήση είναι ενεργή, καθώς και τον απομακρυσμένο αριθμό που συνδέεται από μια κλήση."</string>
- <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"παρεμπόδιση μετάβασης του tablet σε κατάσταση αδράνειας"</string>
- <string name="permlab_wakeLock" product="tv" msgid="2601193288949154131">"αποτροπή μετάβασης της τηλεόρασης στην κατάσταση αδράνειας"</string>
- <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"παρεμπόδιση μετάβασης του τηλεφώνου σε κατάσταση αδράνειας"</string>
+ <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"αποτρέπει την μετάβαση του tablet σε κατάσταση αδράνειας"</string>
+ <string name="permlab_wakeLock" product="tv" msgid="2601193288949154131">"αποτρέπει την μετάβαση της τηλεόρασης σε κατάσταση αδράνειας"</string>
+ <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"αποτρέπει το τηλεφώνο να μεταβεί σε κατάσταση αδράνειας"</string>
<string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"Επιτρέπει στην εφαρμογή την παρεμπόδιση της μετάβασης του tablet σε κατάσταση αδράνειας."</string>
<string name="permdesc_wakeLock" product="tv" msgid="3208534859208996974">"Επιτρέπει στην εφαρμογή να εμποδίζει τη μετάβαση της τηλεόρασης στην κατάσταση αδράνειας."</string>
<string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"Επιτρέπει στην εφαρμογή την παρεμπόδιση της μετάβασης του τηλεφώνου σε κατάσταση αδράνειας."</string>
- <string name="permlab_transmitIr" msgid="7545858504238530105">"μετάδοση υπερύθρων"</string>
+ <string name="permlab_transmitIr" msgid="7545858504238530105">"μεταδίδει με υπερύθρες"</string>
<string name="permdesc_transmitIr" product="tablet" msgid="5358308854306529170">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τον πομπό υπερύθρων του tablet."</string>
<string name="permdesc_transmitIr" product="tv" msgid="3926790828514867101">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τον πομπό υπερύθρων της τηλεόρασης."</string>
<string name="permdesc_transmitIr" product="default" msgid="7957763745020300725">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τον πομπό υπερύθρων του τηλεφώνου."</string>
- <string name="permlab_setWallpaper" msgid="6627192333373465143">"ορισμός ταπετσαρίας"</string>
+ <string name="permlab_setWallpaper" msgid="6627192333373465143">"ορίζει ταπετσαρία"</string>
<string name="permdesc_setWallpaper" msgid="7373447920977624745">"Επιτρέπει στην εφαρμογή τον ορισμό της ταπετσαρίας συστήματος."</string>
- <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"ρύθμιση του μεγέθους της ταπετσαρίας σας"</string>
+ <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"ρυθμίζει το μέγεθος της ταπετσαρίας"</string>
<string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"Επιτρέπει στην εφαρμογή τον ορισμό συμβουλών μεγέθους ταπετσαρίας συστήματος."</string>
- <string name="permlab_setTimeZone" msgid="2945079801013077340">"ορισμός ζώνης ώρας"</string>
+ <string name="permlab_setTimeZone" msgid="2945079801013077340">"ορίζει τη ζώνης ώρας"</string>
<string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"Επιτρέπει στην εφαρμογή την αλλαγή της ζώνης ώρας του tablet."</string>
<string name="permdesc_setTimeZone" product="tv" msgid="888864653946175955">"Επιτρέπει στην εφαρμογή να αλλάζει τη ζώνη ώρας της τηλεόρασης."</string>
<string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"Επιτρέπει στην εφαρμογή την αλλαγή της ζώνης ώρας του τηλεφώνου."</string>
- <string name="permlab_getAccounts" msgid="1086795467760122114">"εύρεση λογαριασμών στη συσκευή"</string>
+ <string name="permlab_getAccounts" msgid="1086795467760122114">"βρίσκει λογαριασμούς στη συσκευή"</string>
<string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"Επιτρέπει στην εφαρμογή τη λήψη της λίστας λογαριασμών που υπάρχουν στο tablet. Μπορεί να περιλαμβάνονται τυχόν λογαριασμοί που δημιουργήθηκαν από εφαρμογές που έχετε εγκαταστήσει."</string>
<string name="permdesc_getAccounts" product="tv" msgid="4190633395633907543">"Επιτρέπει στην εφαρμογή να λαμβάνει τη λίστα λογαριασμών που γνωρίζει η τηλεόραση. Αυτό μπορεί να περιλαμβάνει λογαριασμούς που δημιουργούνται από λογαριασμούς που έχετε εγκαταστήσει."</string>
<string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"Επιτρέπει στην εφαρμογή τη λήψη της λίστας λογαριασμών που υπάρχουν στο τηλέφωνο. Μπορεί να περιλαμβάνονται τυχόν λογαριασμοί που δημιουργήθηκαν από εφαρμογές που έχετε εγκαταστήσει."</string>
- <string name="permlab_accessNetworkState" msgid="4951027964348974773">"προβολή συνδέσεων δικτύου"</string>
+ <string name="permlab_accessNetworkState" msgid="4951027964348974773">"βλέπει τις συνδέσεις δικτύου"</string>
<string name="permdesc_accessNetworkState" msgid="8318964424675960975">"Επιτρέπει στην εφαρμογή την προβολή πληροφοριών σχετικά με συνδέσεις δικτύου, όπως ποια δίκτυα υπάρχουν και είναι συνδεδεμένα."</string>
- <string name="permlab_createNetworkSockets" msgid="7934516631384168107">"πλήρης πρόσβαση στο δίκτυο"</string>
+ <string name="permlab_createNetworkSockets" msgid="7934516631384168107">"έχει πλήρη πρόσβαση στο δίκτυο"</string>
<string name="permdesc_createNetworkSockets" msgid="3403062187779724185">"Επιτρέπει στην εφαρμογή τη δημιουργία θέσεων δικτύου και τη χρήση προσαρμοσμένων πρωτοκόλλων δικτύου. Το πρόγραμμα περιήγησης και άλλες εφαρμογές παρέχουν μέσα για την αποστολή δεδομένων στο διαδίκτυο, επομένως η συγκεκριμένη άδεια δεν είναι απαραίτητη για την αποστολή δεδομένων στο διαδίκτυο."</string>
- <string name="permlab_changeNetworkState" msgid="958884291454327309">"αλλαγή συνδεσιμότητας δικτύου"</string>
+ <string name="permlab_changeNetworkState" msgid="958884291454327309">"αλλάζει την συνδεσιμότητα δικτύου"</string>
<string name="permdesc_changeNetworkState" msgid="6789123912476416214">"Επιτρέπει στην εφαρμογή την αλλαγή της κατάστασης συνδεσιμότητας δικτύου."</string>
- <string name="permlab_changeTetherState" msgid="5952584964373017960">"αλλαγή συνδεσιμότητας μέσω σύνδεσης με κινητή συσκευή"</string>
+ <string name="permlab_changeTetherState" msgid="5952584964373017960">"αλλάζει συνδεσιμότητα μέσω σύνδεσης με κινητή συσκευή"</string>
<string name="permdesc_changeTetherState" msgid="1524441344412319780">"Επιτρέπει στην εφαρμογή την αλλαγή της κατάστασης συνδεσιμότητας δικτύου."</string>
- <string name="permlab_accessWifiState" msgid="5202012949247040011">"προβολή συνδέσεων Wi-Fi"</string>
+ <string name="permlab_accessWifiState" msgid="5202012949247040011">"βλέπει τις συνδέσεις Wi-Fi"</string>
<string name="permdesc_accessWifiState" msgid="5002798077387803726">"Επιτρέπει στην εφαρμογή την προβολή πληροφοριών σχετικά με τη δικτύωση Wi-Fi, όπως εάν το Wi-Fi είναι ενεργοποιημένο και τα ονόματα των συνδεδεμένων συσκευών Wi-Fi."</string>
- <string name="permlab_changeWifiState" msgid="6550641188749128035">"σύνδεση και αποσύνδεση από το Wi-Fi"</string>
+ <string name="permlab_changeWifiState" msgid="6550641188749128035">"συνδέεται/αποσυνδέεται από το Wi-Fi"</string>
<string name="permdesc_changeWifiState" msgid="7137950297386127533">"Επιτρέπει στην εφαρμογή τη σύνδεση σε σημεία πρόσβασης Wi-Fi και την αποσύνδεση από αυτά, καθώς και την πραγματοποίηση αλλαγών σε διαμόρφωση συσκευών για δίκτυα Wi-Fi."</string>
- <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"να επιτρέπεται η λήψη πολλαπλής διανομής Wi-Fi"</string>
+ <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"επιτρέπει την λήψη πολλαπλής διανομής Wi-Fi"</string>
<string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Επιτρέπει στην εφαρμογή τη λήψη πακέτων που αποστέλλονται σε όλες τις συσκευές σε ένα δίκτυο Wi-Fi, με χρήση διευθύνσεων πολλαπλής διανομής και όχι απλώς στο tablet σας. Χρησιμοποιεί περισσότερη ενέργεια σε σχέση με τη λειτουργία χωρίς πολλαπλή διανομή."</string>
<string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"Επιτρέπει στην εφαρμογή να λαμβάνει πακέτα που αποστέλλονται σε όλες τις συσκευές σε ένα δίκτυο Wi-Fi, χρησιμοποιώντας διευθύνσεις multicast και όχι μόνο την τηλεόρασή σας. Χρησιμοποιεί περισσότερη ενέργεια από τη λειτουργία χωρίς multicast."</string>
<string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Επιτρέπει στην εφαρμογή τη λήψη πακέτων που αποστέλλονται σε όλες τις συσκευές σε ένα δίκτυο Wi-Fi, με χρήση διευθύνσεων πολλαπλής διανομής και όχι απλώς στο τηλέφωνό σας. Χρησιμοποιεί περισσότερη ενέργεια σε σχέση με τη λειτουργία χωρίς πολλαπλή διανομή."</string>
- <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"πρόσβαση στις ρυθμίσεις Bluetooth"</string>
+ <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"διαβάζει τις ρυθμίσεις Bluetooth"</string>
<string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Επιτρέπει στην εφαρμογή τη διαμόρφωση του τοπικού tablet Bluetooth, τον εντοπισμό και τη σύζευξη με απομακρυσμένες συσκευές."</string>
<string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Επιτρέπει στην εφαρμογή να διαμορφώνει το τοπικό Bluetooth στην τηλεόραση, καθώς και να ανακαλύπτει απομακρυσμένες συσκευές και να συνδέεται μαζί τους."</string>
<string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Επιτρέπει στην εφαρμογή τη διαμόρφωση του τοπικού tablet Bluetooth, τον εντοπισμό και τη σύζευξη με απομακρυσμένες συσκευές."</string>
@@ -426,17 +425,17 @@
<string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Επιτρέπει στην εφαρμογή τη σύνδεση στο tablet και την αποσύνδεση από αυτό, από δίκτυα WiMAX."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"Επιτρέπει στην εφαρμογή να συνδέει και να αποσυνδέει την τηλεόραση από δίκτυα WiMAX."</string>
<string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Επιτρέπει στην εφαρμογή τη σύνδεση στο τηλέφωνο και την αποσύνδεση από αυτό, από δίκτυα WiMAX."</string>
- <string name="permlab_bluetooth" msgid="6127769336339276828">"σύζευξη με συσκευές Bluetooth"</string>
+ <string name="permlab_bluetooth" msgid="6127769336339276828">"πραγματοποιεί σύζευξη με συσκευές Bluetooth"</string>
<string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Επιτρέπει στην εφαρμογή να προβάλλει τη διαμόρφωση του Bluetooth στο tablet, καθώς και να πραγματοποιεί και να αποδέχεται συνδέσεις με συνδεδεμένες συσκευές."</string>
<string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Επιτρέπει στην εφαρμογή να προβάλλει τη διαμόρφωση του Bluetooth στην τηλεόραση, καθώς και να δημιουργεί και να αποδέχεται συνδέσεις με συσκευές σε σύζευξη."</string>
<string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Επιτρέπει στην εφαρμογή να προβάλλει τη διαμόρφωση του Bluetooth στο τηλέφωνο, καθώς και να πραγματοποιεί και να αποδέχεται συνδέσεις με συνδεδεμένες συσκευές."</string>
- <string name="permlab_nfc" msgid="4423351274757876953">"έλεγχος Επικοινωνίας κοντινού πεδίου (Near Field Communication)"</string>
+ <string name="permlab_nfc" msgid="4423351274757876953">"ελέγχει την Επικοινωνία κοντινού πεδίου (FNC)"</string>
<string name="permdesc_nfc" msgid="7120611819401789907">"Επιτρέπει στην εφαρμογή την επικοινωνία με ετικέτες, κάρτες και αναγνώστες της Επικοινωνίας κοντινού πεδίου (NFC)."</string>
- <string name="permlab_disableKeyguard" msgid="3598496301486439258">"απενεργοποίηση κλειδώματος οθόνης"</string>
+ <string name="permlab_disableKeyguard" msgid="3598496301486439258">"απενεργοποιεί το κλείδωμα οθόνης"</string>
<string name="permdesc_disableKeyguard" msgid="6034203065077122992">"Επιτρέπει στην εφαρμογή την απενεργοποίηση του κλειδώματος πληκτρολογίου και άλλης σχετικής ασφάλειας με κωδικό πρόσβασης. Για παράδειγμα, το κλείδωμα πληκτρολογίου στο τηλέφωνο απενεργοποιείται όταν λαμβάνεται εισερχόμενη τηλεφωνική κλήση και ενεργοποιείται ξανά όταν η κλήση τερματιστεί."</string>
- <string name="permlab_manageFingerprint" msgid="5640858826254575638">"διαχείριση εξοπλισμού μοναδικού χαρακτηριστικού"</string>
+ <string name="permlab_manageFingerprint" msgid="5640858826254575638">"διαχειρίζεται τον εξοπλισμό δακτυλικού αποτυπώματος"</string>
<string name="permdesc_manageFingerprint" msgid="178208705828055464">"Επιτρέπει στην εφαρμογή να επικαλείται μεθόδους για την προσθήκη και τη διαγραφή προτύπων μοναδικού χαρακτηριστικού για χρήση."</string>
- <string name="permlab_useFingerprint" msgid="3150478619915124905">"χρήση εξοπλισμού μοναδικού χαρακτηριστικού"</string>
+ <string name="permlab_useFingerprint" msgid="3150478619915124905">"χρησιμοποιεί τον εξοπλισμό δακτυλικού αποτυπώματος"</string>
<string name="permdesc_useFingerprint" msgid="9165097460730684114">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί εξοπλισμό μοναδικού χαρακτηριστικού για έλεγχο ταυτότητας"</string>
<string name="fingerprint_acquired_partial" msgid="735082772341716043">"Εντοπίστηκε μερικό μοναδικό χαρακτηριστικό. Δοκιμάστε ξανά."</string>
<string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Δεν ήταν δυνατή η επεξεργασία του μοναδικού χαρακτηριστικού. Δοκιμάστε ξανά."</string>
@@ -455,65 +454,65 @@
<string-array name="fingerprint_error_vendor">
</string-array>
<string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Εικονίδιο δακτυλικών αποτυπωμάτων"</string>
- <string name="permlab_readSyncSettings" msgid="6201810008230503052">"ανάγνωση ρυθμίσεων συγχρονισμού"</string>
+ <string name="permlab_readSyncSettings" msgid="6201810008230503052">"διαβάζει τις ρυθμίσεις συγχρονισμού"</string>
<string name="permdesc_readSyncSettings" msgid="2706745674569678644">"Επιτρέπει στην εφαρμογή την ανάγνωση των ρυθμίσεων συγχρονισμού για έναν λογαριασμό. Για παράδειγμα, αυτό μπορεί να καθορίσει εάν η εφαρμογή \"Άτομα\" είναι συγχρονισμένη με έναν λογαριασμό."</string>
- <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"εναλλαγή ενεργοποίησης και απενεργοποίησης συγχρονισμού"</string>
+ <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"ενεργοποιεί/απενεργοποιεί τον συγχρονισμό"</string>
<string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"Επιτρέπει σε μια εφαρμογή την τροποποίηση των ρυθμίσεων συγχρονισμού για έναν λογαριασμό. Για παράδειγμα, αυτό μπορεί να χρησιμοποιηθεί για να ενεργοποιηθεί ο συγχρονισμός της εφαρμογής \"Άτομα\" με έναν λογαριασμό."</string>
- <string name="permlab_readSyncStats" msgid="7396577451360202448">"ανάγνωση στατιστικών συγχρονισμού"</string>
+ <string name="permlab_readSyncStats" msgid="7396577451360202448">"διαβάζει στατιστικά στοιχεία συγχρονισμού"</string>
<string name="permdesc_readSyncStats" msgid="1510143761757606156">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των στατιστικών στοιχείων συγχρονισμού για έναν λογαριασμό, συμπεριλαμβανομένων του ιστορικού των συμβάντων συγχρονισμού και του όγκου των δεδομένων που συγχρονίζονται."</string>
<string name="permlab_sdcardRead" product="nosdcard" msgid="367275095159405468">"ανάγν. περιεχ. αποθηκ. χώρ.USB"</string>
- <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"ανάγνωση του περιεχομένου της κάρτας SD"</string>
+ <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"διαβάζει το περιεχομένο της κάρτας SD"</string>
<string name="permdesc_sdcardRead" product="nosdcard" msgid="3446988712598386079">"Επιτρέπει στην εφαρμογή την ανάγνωση του περιεχομένου του αποθηκευτικού σας χώρου USB."</string>
<string name="permdesc_sdcardRead" product="default" msgid="2607362473654975411">"Επιτρέπει στην εφαρμογή την ανάγνωση του περιεχομένου της κάρτας SD."</string>
- <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"τροποποίηση ή διαγραφή του USB"</string>
- <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"τροποποίηση ή διαγραφή των περιεχομένων της κάρτας SD"</string>
+ <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"τροποποιεί/διαγράφει το USB"</string>
+ <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"τροποποιεί ή διαγράφει τα περιεχόμενα της κάρτας SD"</string>
<string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"Επιτρέπει στην εφαρμογή την εγγραφή στον αποθηκευτικό χώρο USB."</string>
<string name="permdesc_sdcardWrite" product="default" msgid="4337417790936632090">"Επιτρέπει στην εφαρμογή την εγγραφή στην κάρτα SD."</string>
- <string name="permlab_use_sip" msgid="2052499390128979920">"πραγματοποίηση/λήψη κλήσεων SIP"</string>
+ <string name="permlab_use_sip" msgid="2052499390128979920">"πραγματοποιεί/λαμβάνει κλήσεις SIP"</string>
<string name="permdesc_use_sip" msgid="2297804849860225257">"Επιτρέπει στην εφαρμογή να πραγματοποιεί και να λαμβάνει κλήσεις SIP."</string>
- <string name="permlab_register_sim_subscription" msgid="3166535485877549177">"εγγραφή νέων συνδέσεων SIM τηλεπικοινωνιών"</string>
+ <string name="permlab_register_sim_subscription" msgid="3166535485877549177">"πραγματοποιεί εγγραφή νέων συνδέσεων SIM τηλεπικοινωνιών"</string>
<string name="permdesc_register_sim_subscription" msgid="2138909035926222911">"Επιτρέπει στην εφαρμογή την εγγραφή νέων συνδέσεων SIM τηλεπικοινωνιών."</string>
- <string name="permlab_register_call_provider" msgid="108102120289029841">"εγγραφή νέων συνδέσεων τηλεπικοινωνιών"</string>
+ <string name="permlab_register_call_provider" msgid="108102120289029841">"πραγματοποιεί εγγραφή των νέων συνδέσεων τηλεπικοινωνιών"</string>
<string name="permdesc_register_call_provider" msgid="7034310263521081388">"Επιτρέπει στην εφαρμογή την εγγραφή νέων συνδέσεων τηλεπικοινωνιών."</string>
- <string name="permlab_connection_manager" msgid="1116193254522105375">"διαχείριση των συνδέσεων τηλεπικοινωνιών"</string>
+ <string name="permlab_connection_manager" msgid="1116193254522105375">"διαχειρίζεται τις συνδέσεις τηλεπικοινωνιών"</string>
<string name="permdesc_connection_manager" msgid="5925480810356483565">"Επιτρέπει στην εφαρμογή να διαχειρίζεται τις συνδέσεις τηλεπικοινωνιών."</string>
- <string name="permlab_bind_incall_service" msgid="6773648341975287125">"αλληλεπίδραση με την οθόνη κατά τη διάρκεια κλήσης"</string>
+ <string name="permlab_bind_incall_service" msgid="6773648341975287125">"αλληλεπιδρά με την οθόνη κατά τη διάρκεια κλήσης"</string>
<string name="permdesc_bind_incall_service" msgid="8343471381323215005">"Επιτρέπει στην εφαρμογή να ελέγχει πότε και πώς βλέπει ο χρήστης την οθόνη κατά τη διάρκεια κλήσης."</string>
- <string name="permlab_bind_connection_service" msgid="3557341439297014940">"αλληλεπίδραση με υπηρεσίες τηλεφωνίας"</string>
+ <string name="permlab_bind_connection_service" msgid="3557341439297014940">"αλληλεπιδρά με υπηρεσίες τηλεφωνίας"</string>
<string name="permdesc_bind_connection_service" msgid="4008754499822478114">"Επιτρέπει στην εφαρμογή να αλληλεπιδρά με υπηρεσίες τηλεφωνίας για την πραγματοποίηση/λήψη κλήσεων."</string>
- <string name="permlab_control_incall_experience" msgid="9061024437607777619">"παροχή εμπειρίας χρήστη κατά τη διάρκεια κλήσης"</string>
+ <string name="permlab_control_incall_experience" msgid="9061024437607777619">"παρέχει εμπειρία χρήστη κατά τη διάρκεια κλήσης"</string>
<string name="permdesc_control_incall_experience" msgid="915159066039828124">"Επιτρέπει στην εφαρμογή να παρέχει μια εμπειρία στο χρήστη κατά τη διάρκεια κλήσης."</string>
- <string name="permlab_readNetworkUsageHistory" msgid="7862593283611493232">"ανάγνωση ιστορικών δεδομένων χρήσης δικτύου"</string>
+ <string name="permlab_readNetworkUsageHistory" msgid="7862593283611493232">"διαβάζει ιστορικά στοιχεία δεδομένων χρήσης δικτύου"</string>
<string name="permdesc_readNetworkUsageHistory" msgid="7689060749819126472">"Επιτρέπει στην εφαρμογή την ανάγνωση ιστορικών στοιχείων χρήσης δικτύου για συγκεκριμένα δίκτυα και εφαρμογές."</string>
- <string name="permlab_manageNetworkPolicy" msgid="2562053592339859990">"διαχείριση πολιτικής δικτύου"</string>
+ <string name="permlab_manageNetworkPolicy" msgid="2562053592339859990">"διαχειρίζεται την πολιτική δικτύου"</string>
<string name="permdesc_manageNetworkPolicy" msgid="7537586771559370668">"Επιτρέπει στην εφαρμογή τη διαχείριση των πολιτικών δικτύου και τον ορισμό κανόνων για ορισμένες εφαρμογές."</string>
- <string name="permlab_modifyNetworkAccounting" msgid="5088217309088729650">"τροποποίηση υπολογισμού χρήσης δικτύου"</string>
+ <string name="permlab_modifyNetworkAccounting" msgid="5088217309088729650">"τροποποιεί τον υπολογισμό χρήσης δικτύου"</string>
<string name="permdesc_modifyNetworkAccounting" msgid="5443412866746198123">"Επιτρέπει στην εφαρμογή την τροποποίηση του τρόπου υπολογισμού της χρήσης δικτύου έναντι των εφαρμογών. Δεν προορίζεται για χρήση από συνήθεις εφαρμογές."</string>
- <string name="permlab_accessNotifications" msgid="7673416487873432268">"πρόσβαση στις ειδοποιήσεις"</string>
+ <string name="permlab_accessNotifications" msgid="7673416487873432268">"έχει πρόσβαση στις ειδοποιήσεις"</string>
<string name="permdesc_accessNotifications" msgid="458457742683431387">"Επιτρέπει στην εφαρμογή να ανακτά, να εξετάζει και να απαλείφει ειδοποιήσεις, συμπεριλαμβανομένων εκείνων που δημοσιεύονται από άλλες εφαρμογές."</string>
- <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"δέσμευση σε υπηρεσία ακρόασης ειδοποίησης"</string>
+ <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"συνδέεται σε υπηρεσία ακρόασης ειδοποίησης"</string>
<string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας υπηρεσίας ακρόασης ειδοποιήσεων. Δεν απαιτείται σε κανονικές εφαρμογές."</string>
- <string name="permlab_bindConditionProviderService" msgid="1180107672332704641">"σύνδεση σε μια υπηρεσία παρόχου συνθηκών"</string>
+ <string name="permlab_bindConditionProviderService" msgid="1180107672332704641">"δεσμεύεται σε μια υπηρεσία παρόχου συνθηκών"</string>
<string name="permdesc_bindConditionProviderService" msgid="1680513931165058425">"Επιτρέπει στον κάτοχο τη σύνδεση στη διεπαφή ανωτάτου επιπέδου ενός παρόχου συνθηκών. Δεν απαιτείται για κανονικές εφαρμογές."</string>
- <string name="permlab_bindDreamService" msgid="4153646965978563462">"δέσμευση σε υπηρεσία dream"</string>
+ <string name="permlab_bindDreamService" msgid="4153646965978563462">"δεσμεύεται σε υπηρεσία dream"</string>
<string name="permdesc_bindDreamService" msgid="7325825272223347863">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας υπηρεσίας dream. Δεν απαιτείται σε κανονικές εφαρμογές."</string>
- <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"κλήση της εφαρμογής διαμόρφωσης που παρέχεται από την εταιρεία κινητής τηλεφωνίας"</string>
+ <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"καλέι την εφαρμογή διαμόρφωσης του παρόχου κινητής τηλεφωνίας"</string>
<string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Επιτρέπει στον κάτοχο την κλήση της εφαρμογής διαμόρφωσης που παρέχεται από την εταιρεία κινητής τηλεφωνίας. Δεν απαιτείται για κανονικές εφαρμογές."</string>
- <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"λήψη παρατηρήσεων σχετικά με την κατάσταση δικτύου"</string>
+ <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"ανιχνεύει παρατηρήσεις σχετικά με την κατάσταση δικτύου"</string>
<string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Επιτρέπει σε μια εφαρμογή να λαμβάνει παρατηρήσεις σχετικά με την κατάσταση δικτύου. Δεν θα πρέπει να απαιτείται ποτέ για κανονικές εφαρμογές."</string>
<string name="permlab_setInputCalibration" msgid="4902620118878467615">"αλλαγή βαθμονόμησης της συσκευής εισόδου"</string>
<string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Επιτρέπει στην εφαρμογή να τροποποιεί τις παραμέτρους βαθμονόμησης της οθόνης αφής. Δεν απαιτείται για τις κανονικές εφαρμογές."</string>
- <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"πρόσβαση σε πιστοποιητικά DRM"</string>
+ <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"έχει πρόσβαση σε πιστοποιητικά DRM"</string>
<string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Επιτρέπει σε μια εφαρμογή να παρέχει και να χρησιμοποιεί πιστοποιητικά DRM. Δεν θα χρειαστεί ποτέ για κανονικές εφαρμογές."</string>
<string name="permlab_handoverStatus" msgid="7820353257219300883">"λήψη κατάστασης μεταφοράς Android Beam"</string>
<string name="permdesc_handoverStatus" msgid="4788144087245714948">"Επιτρέπει σε αυτήν την εφαρμογή να λαμβάνει πληροφορίες σχετικά με τις τρέχουσες μεταφορές Android Beam"</string>
- <string name="permlab_removeDrmCertificates" msgid="7044888287209892751">"κατάργηση πιστοποιητικών DRM"</string>
+ <string name="permlab_removeDrmCertificates" msgid="7044888287209892751">"καταργεί πιστοποιητικά DRM"</string>
<string name="permdesc_removeDrmCertificates" msgid="7272999075113400993">"Επιτρέπει σε μια εφαρμογή την κατάργηση πιστοποιητικών DRM. Δεν χρειάζεται ποτέ για κανονικές εφαρμογές."</string>
- <string name="permlab_bindCarrierMessagingService" msgid="1490229371796969158">"δέσμευση σε υπηρεσία ανταλλαγής μηνυμάτων εταιρείας κινητής τηλεφωνίας"</string>
+ <string name="permlab_bindCarrierMessagingService" msgid="1490229371796969158">"δεσμεύεται σε υπηρεσία ανταλλαγής μηνυμάτων παρόχου κινητής τηλεφωνίας"</string>
<string name="permdesc_bindCarrierMessagingService" msgid="2762882888502113944">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας υπηρεσίας ανταλλαγής μηνυμάτων εταιρείας κινητής τηλεφωνίας. Δεν απαιτείται για συνήθεις εφαρμογές."</string>
- <string name="permlab_bindCarrierServices" msgid="3233108656245526783">"δέσμευση σε υπηρεσίες εταιρείας κινητής τηλεφωνίας"</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="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>
@@ -793,17 +792,17 @@
<string name="autofill_parish" msgid="8202206105468820057">"Ενορία"</string>
<string name="autofill_area" msgid="3547409050889952423">"Περιοχή"</string>
<string name="autofill_emirate" msgid="2893880978835698818">"Εμιράτο"</string>
- <string name="permlab_readHistoryBookmarks" msgid="3775265775405106983">"ανάγνωση των σελιδοδεικτών και του ιστορικού ιστού σας"</string>
+ <string name="permlab_readHistoryBookmarks" msgid="3775265775405106983">"διαβάζει τους σελιδοδείκτες και το ιστορικού ιστού"</string>
<string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"Επιτρέπει στην εφαρμογή την ανάγνωση του ιστορικού όλων των διευθύνσεων URL που έχει επισκεφτεί το πρόγραμμα περιήγησης, καθώς και όλων των σελιδοδεικτών του προγράμματος περιήγησης. Σημείωση: αυτή η άδεια ίσως να μην μπορεί να εφαρμοστεί από τρίτα προγράμματα περιήγησης ή άλλες εφαρμογές με δυνατότητες περιήγησης ιστού."</string>
- <string name="permlab_writeHistoryBookmarks" msgid="3714785165273314490">"εγγραφή σελιδοδεικτών και ιστορικού ιστού"</string>
+ <string name="permlab_writeHistoryBookmarks" msgid="3714785165273314490">"εγγράφει σελιδοδείκτες και ιστορικό ιστού"</string>
<string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"Επιτρέπει στην εφαρμογή την τροποποίηση του ιστορικού του προγράμματος περιήγησης ή των σελιδοδεικτών που έχουν αποθηκευτεί στο tablet σας. Αυτό μπορεί να δίνει τη δυνατότητα στην εφαρμογή να διαγράφει ή να τροποποιεί δεδομένα του προγράμματος περιήγησης. Σημείωση: αυτή η άδεια ίσως να μην μπορεί να εφαρμοστεί από τρίτα προγράμματα περιήγησης ή άλλες εφαρμογές με δυνατότητες περιήγησης ιστού."</string>
<string name="permdesc_writeHistoryBookmarks" product="tv" msgid="7007393823197766548">"Επιτρέπει στην εφαρμογή να τροποποιεί το ιστορικό του προγράμματος περιήγησης ή τους σελιδοδείκτες που είναι αποθηκευμένοι στην τηλεόρασή σας. Σημείωση: Αυτό το δικαίωμα δεν πρέπει να εφαρμόζεται από τρίτα προγράμματα περιήγησης ή άλλες εφαρμογές με δυνατότητες περιήγησης στον ιστό."</string>
<string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Επιτρέπει στην εφαρμογή την τροποποίηση του ιστορικού του προγράμματος περιήγησης ή των σελιδοδεικτών που έχουν αποθηκευτεί στο τηλέφωνό σας. Αυτό μπορεί να δίνει τη δυνατότητα στην εφαρμογή να διαγράφει ή να τροποποιεί δεδομένα του προγράμματος περιήγησης. Σημείωση: αυτή η άδεια ίσως να μην μπορεί να εφαρμοστεί από τρίτα προγράμματα περιήγησης ή άλλες εφαρμογές με δυνατότητες περιήγησης ιστού."</string>
- <string name="permlab_setAlarm" msgid="1379294556362091814">"ρύθμιση ξυπνητηριού"</string>
+ <string name="permlab_setAlarm" msgid="1379294556362091814">"ρυθμίζει το ξυπνητήρι"</string>
<string name="permdesc_setAlarm" msgid="316392039157473848">"Επιτρέπει στην εφαρμογή τη ρύθμιση μιας ειδοποίησης σε μια εγκατεστημένη εφαρμογή ξυπνητηριού. Ορισμένες εφαρμογές ξυπνητηριού ενδέχεται να μην μπορούν να ενσωματώσουν αυτήν τη λειτουργία."</string>
- <string name="permlab_addVoicemail" msgid="5525660026090959044">"προσθήκη τηλεφωνητή"</string>
+ <string name="permlab_addVoicemail" msgid="5525660026090959044">"προσθέτει τηλεφωνητή"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Επιτρέπει στην εφαρμογή να προσθέτει μηνύματα στα εισερχόμενα του αυτόματου τηλεφωνητή σας."</string>
- <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"τροποποίηση δικαιωμάτων γεωγραφικής θέσης του Προγράμματος περιήγησης"</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"τροποποιεί δικαιώματα γεωγραφικής θέσης του Προγράμματος περιήγησης"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Επιτρέπει στην εφαρμογή την τροποποίηση των αδειών γεωτοποθεσίας του Προγράμματος περιήγησης. Τυχόν κακόβουλες εφαρμογές ενδέχεται να το χρησιμοποιήσουν για να επιτρέψουν την αποστολή πληροφοριών τοποθεσίας σε αυθαίρετους ιστότοπους."</string>
<string name="save_password_message" msgid="767344687139195790">"Θέλετε το πρόγραμμα περιήγησης να διατηρήσει αυτόν τον κωδικό πρόσβασης;"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Να μην γίνει τώρα"</string>
@@ -1203,11 +1202,11 @@
<string name="ext_media_status_formatting" msgid="1085079556538644861">"Διαμόρφωση…"</string>
<string name="ext_media_status_missing" msgid="5638633895221670766">"Δεν έχει εισαχθεί"</string>
<string name="activity_list_empty" msgid="1675388330786841066">"Δεν βρέθηκαν δραστηριότητες που να συμφωνούν με τα κριτήρια."</string>
- <string name="permlab_route_media_output" msgid="6243022988998972085">"δρομολόγηση εξόδου μέσων"</string>
+ <string name="permlab_route_media_output" msgid="6243022988998972085">"δρομολογεί την έξοδο μέσων"</string>
<string name="permdesc_route_media_output" msgid="4932818749547244346">"Επιτρέπει σε μια εφαρμογή τη διαγραφή διαδρομής δεδομένων εξόδου μέσων σε άλλες εξωτερικές συσκευές."</string>
- <string name="permlab_readInstallSessions" msgid="3713753067455750349">"ανάγνωση περιόδων σύνδεσης εγκατάστασης"</string>
+ <string name="permlab_readInstallSessions" msgid="3713753067455750349">"διαβάζει τις περιόδους σύνδεσης εγκατάστασης"</string>
<string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των περιόδων σύνδεσης εγκατάστασης. Αυτό της επιτρέπει να βλέπει λεπτομέρειες σχετικά με τις εγκαταστάσεις του ενεργού πακέτου."</string>
- <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"αίτημα εγκατάστασης πακέτων"</string>
+ <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ζητά πακέτα εγκατάστασης"</string>
<string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Επιτρέπει σε μια εφαρμογή να ζητά εγκατάσταση πακέτων."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Πατήστε δύο φορές για έλεγχο εστίασης"</string>
<string name="gadget_host_error_inflating" msgid="4882004314906466162">"Δεν ήταν δυνατή η προσθήκη του γραφικού στοιχείου."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Μονάδα USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Αποθηκευτικός χώρος USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Επεξεργασία"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Προειδοποίηση χρήσης δεδομένων"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Ειδοποίηση χρήσης δεδομένων"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Πατήστε για προβολή χρήσης/ρυθμ."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Συμπλ. το όριο δεδομένων 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Συμπλ. το όριο δεδομένων 4G"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 6342966..38f2b88 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Phone options"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Screen lock"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB drive"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB storage"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Data usage warning"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Data usage alert"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tap to view usage and settings."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G data limit reached"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G data limit reached"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 6342966..38f2b88 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Phone options"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Screen lock"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB drive"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB storage"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Data usage warning"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Data usage alert"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tap to view usage and settings."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G data limit reached"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G data limit reached"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 6342966..38f2b88 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Phone options"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Screen lock"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Power off"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Bug report"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Take bug report"</string>
<string name="bugreport_message" msgid="398447048750350456">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB drive"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB storage"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Data usage warning"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Data usage alert"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tap to view usage and settings."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G data limit reached"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G data limit reached"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 0ba371f..1277acc 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opciones de dispositivo"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueo de pantalla"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Apagar"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergencias"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Informe de errores"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Iniciar informe de errores"</string>
<string name="bugreport_message" msgid="398447048750350456">"Se recopilará información sobre el estado actual de tu dispositivo, que se enviará por correo. Pasarán unos minutos desde que se inicie el informe de errores hasta que se envíe, por lo que te recomendamos que tengas paciencia."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unidad USB de <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Almacenamiento USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editar"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Advertencia de uso de datos"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta por el uso de datos"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Presiona para uso y opciones."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Límite de datos 2G-3G alcanzado"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Límite de datos 4G alcanzado"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index d60fa83..77b43b1 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opciones del teléfono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueo de pantalla"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Apagar"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergencia"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Informe de error"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Crear informe de errores"</string>
<string name="bugreport_message" msgid="398447048750350456">"Se recopilará información sobre el estado actual de tu dispositivo y se enviará por correo electrónico. Pasarán unos minutos desde que empiece a generarse el informe de errores hasta que se envíe."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unidad USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Almacenamiento USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editar"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Advertencia de uso de datos"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta sobre el uso de datos"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Toca para ver uso y ajustes."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Límite de datos 2G-3G alcanzado"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Límite de datos 4G alcanzado"</string>
@@ -1570,7 +1569,7 @@
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado por tu administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado por tu administrador"</string>
<string name="battery_saver_description" msgid="1960431123816253034">"Para ayudar a mejorar la duración de la batería, la función de ahorro de energía reduce el rendimiento del dispositivo y limita la vibración, los servicios de ubicación y la mayor parte de la transmisión de datos en segundo plano. Es posible que las aplicaciones que se sincronizan, como las de correo y mensajes, no se actualicen a menos que las abras.\n\nLa función de ahorro de energía se desactiva automáticamente cuando el dispositivo se está cargando."</string>
- <string name="data_saver_description" msgid="6015391409098303235">"El Economizador de Datos evita que algunas aplicaciones envíen o reciban datos en segundo plano, lo que permite reducir el uso de datos. Una aplicación activa podrá acceder a los datos, aunque con menos frecuencia. Esto significa que, por ejemplo, algunas imágenes no se muestren hasta que no las toques."</string>
+ <string name="data_saver_description" msgid="6015391409098303235">"El ahorro de datos evita que algunas aplicaciones envíen o reciban datos en segundo plano, lo que permite reducir el uso de datos. Una aplicación activa podrá acceder a los datos, aunque con menos frecuencia. Esto significa que, por ejemplo, algunas imágenes no se muestren hasta que no las toques."</string>
<string name="data_saver_enable_title" msgid="4674073932722787417">"¿Activar ahorro de datos?"</string>
<string name="data_saver_enable_button" msgid="7147735965247211818">"Activar"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 15fa79f..d7ee50c 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefonivalikud"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekraanilukk"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Lülita välja"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Hädaabi"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Veaaruanne"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Veaaruande võtmine"</string>
<string name="bugreport_message" msgid="398447048750350456">"Nii kogutakse teavet teie seadme praeguse oleku kohta, et saata see meilisõnumina. Enne kui saate veaaruande ära saata, võtab selle loomine natuke aega; varuge kannatust."</string>
@@ -253,7 +252,7 @@
<string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
<string name="permgroupdesc_sms" msgid="4656988620100940350">"saata ja vaadata SMS-sõnumeid"</string>
<string name="permgrouplab_storage" msgid="1971118770546336966">"Mäluruum"</string>
- <string name="permgroupdesc_storage" msgid="637758554581589203">"juurde pääseda seadmesse salvestatud fotodele, meediale ja failidele"</string>
+ <string name="permgroupdesc_storage" msgid="637758554581589203">"juurde pääseda seadmesse salvestatud fotodele, meediasisule ja failidele"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Mikrofon"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"heli salvestamine"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Kaamera"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Tootja <xliff:g id="MANUFACTURER">%s</xliff:g> USB-ketas"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-mäluseade"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Muuda"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Andmete kasutamise hoiatus"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Andmekasutuse hoiatus"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Puudutage kasutuse/seadete vaat."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-, 3G-andmeside limiit on täis"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G-andmeside limiit on täis"</string>
diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml
index 47e64c3..e7f3f02 100644
--- a/core/res/res/values-eu-rES/strings.xml
+++ b/core/res/res/values-eu-rES/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefonoaren aukerak"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Pantailaren blokeoa"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Itzali"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Larrialdiak"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Akatsen txostena"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Sortu akatsen txostena"</string>
<string name="bugreport_message" msgid="398447048750350456">"Gailuaren uneko egoerari buruzko informazioa bilduko da, mezu elektroniko gisa bidaltzeko. Minutu batzuk igaroko dira akatsen txostena sortzen hasten denetik bidaltzeko prest egon arte. Itxaron, mesedez."</string>
@@ -744,7 +743,7 @@
<string name="keyguard_accessibility_user_selector" msgid="1226798370913698896">"Erabiltzaile-hautatzailea"</string>
<string name="keyguard_accessibility_status" msgid="8008264603935930611">"Egoera"</string>
<string name="keyguard_accessibility_camera" msgid="8904231194181114603">"Kamera"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="262209654292161806">"Multimedia-kontrolak"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="262209654292161806">"Multimedia kontrolatzeko aukerak"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="8736853615588828197">"Widgetak berrantolatzen hasi da."</string>
<string name="keyguard_accessibility_widget_reorder_end" msgid="7170190950870468320">"Widgetak berrantolatu dira."</string>
<string name="keyguard_accessibility_widget_deleted" msgid="4426204263929224434">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g> widgeta ezabatu da."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB unitatea"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB memoria"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editatu"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Datuen erabilerari buruzko abisua"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Datu-erabilerari buruzko abisua"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Sakatu erabilera eta ezarpenak ikusteko."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2-3 GB-ko mugara iritsi zara"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4 GB-ko mugara iritsi zara"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 95c0db7..374183f 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"درایو USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"حافظهٔ USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ویرایش"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"هشدار میزان استفاده از داده"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"هشدار مصرف داده"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"برای مشاهده مصرف و تنظیمات ضربه بزنید."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"به حد مجاز مصرف داده 2G-3G رسید"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"به حد مجاز مصرف داده 4G رسید"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index e11b2ca..9b6d57c 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Puhelimen asetukset"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Näytön lukitus"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Katkaise virta"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Hätäpuhelu"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Virheraportti"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Luo virheraportti"</string>
<string name="bugreport_message" msgid="398447048750350456">"Toiminto kerää tietoja laitteen tilasta ja lähettää ne sähköpostitse. Virheraportti on valmis lähetettäväksi hetken kuluttua - kiitos kärsivällisyydestäsi."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-asema: <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-tallennustila"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Muokkaa"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Tiedonsiirtovaroitus"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Datankäyttövaroitus"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Käyttö & asetukset napauttamalla"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G-tietojen raja saavutettu"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G-tietojen raja saavutettu"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index c1e1808..034b291 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Options du téléphone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Verrouillage de l\'écran"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Éteindre"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Urgence"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Rapport de bogue"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Créer un rapport de bogue"</string>
<string name="bugreport_message" msgid="398447048750350456">"Cela permet de recueillir des informations concernant l\'état actuel de votre appareil. Ces informations sont ensuite envoyées sous forme de courriel. Merci de patienter pendant la préparation du rapport de bogue. Cette opération peut prendre quelques instants."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Clé USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Mémoire de stockage USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Modifier"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Avertissement utilisation données"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerte d\'utilisation des données"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Touch. pour aff. util. et param."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Limite de données 2G-3G atteinte"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Limite de données 4G atteinte"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 850301f..1e5f6a1 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Options du téléphone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Verrouillage de l\'écran"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Éteindre"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Urgences"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Rapport de bug"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Créer un rapport de bug"</string>
<string name="bugreport_message" msgid="398447048750350456">"Cela permet de recueillir des informations concernant l\'état actuel de votre appareil. Ces informations sont ensuite envoyées sous forme d\'e-mail. Merci de patienter pendant la préparation du rapport de bug. Cette opération peut prendre quelques instants."</string>
@@ -995,7 +994,7 @@
<string name="noApplications" msgid="2991814273936504689">"Aucune application ne peut effectuer cette action."</string>
<string name="aerr_application" msgid="250320989337856518">"<xliff:g id="APPLICATION">%1$s</xliff:g> a cessé de fonctionner."</string>
<string name="aerr_process" msgid="6201597323218674729">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a cessé de fonctionner."</string>
- <string name="aerr_application_repeated" msgid="3146328699537439573">"<xliff:g id="APPLICATION">%1$s</xliff:g> ne cesse de s\'arrêter."</string>
+ <string name="aerr_application_repeated" msgid="3146328699537439573">"<xliff:g id="APPLICATION">%1$s</xliff:g> s\'arrête systématiquement"</string>
<string name="aerr_process_repeated" msgid="6235302956890402259">"Le processus \"<xliff:g id="PROCESS">%1$s</xliff:g>\" ne cesse de s\'arrêter."</string>
<string name="aerr_restart" msgid="7581308074153624475">"Rouvrir l\'application"</string>
<string name="aerr_report" msgid="5371800241488400617">"Envoyer des commentaires"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Clé USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Mémoire de stockage USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Modifier"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Avertissement utilisation données"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerte de consommation des données"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Appuyez pour conso/paramètres."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Limite de données 2G-3G atteinte"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Limite de données 4G atteinte"</string>
@@ -1571,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-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml
index 8a37cff..704667a 100644
--- a/core/res/res/values-gl-rES/strings.xml
+++ b/core/res/res/values-gl-rES/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opcións de teléfono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueo da pantalla"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Apagar"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emerxencias"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Informe de erros"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Crear informe de erros"</string>
<string name="bugreport_message" msgid="398447048750350456">"Este informe recompilará información acerca do estado actual do teu dispositivo para enviala en forma de mensaxe de correo electrónico. O informe de erros tardará un pouco en completarse desde o seu inicio ata que estea preparado para enviarse, polo que che recomendamos que teñas paciencia."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unidade USB de <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"almacenamento USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editar"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Aviso de uso de datos"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta de uso de datos"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Toca para uso e configuración."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Límite de datos de 2G-3G acadado"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Límite de datos de 4G acadado"</string>
diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml
index 2cd3da8..cd3e7be 100644
--- a/core/res/res/values-gu-rIN/strings.xml
+++ b/core/res/res/values-gu-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ડ્રાઇવ"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB સંગ્રહ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"સંપાદિત કરો"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ડેટા વપરાશ ચેતવણી"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ડેટા વપરાશ ચેતવણી"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"વપરાશ અને સેટિંગ્સ જોવા ટૅપ કરો."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ડેટા મર્યાદા પર પહોંચ્યાં"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ડેટા મર્યાદા સુધી પહોંચ્યાં"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index d87f747..c8b3256 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB डिस्क"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB मेमोरी"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"संपादित करें"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"डेटा उपयोग की चेतावनी"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"डेटा उपयोग की सूचना"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"उपयोग व सेटिंग देखने हेतु टैप करें."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G डेटा सीमा पूर्ण हो गई"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G डेटा सीमा पूर्ण हो गई"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index ed55b3f..3fc3346 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -216,8 +216,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opcije telefona"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zaključavanje zaslona"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Isključi"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Hitno"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Izvješće o bugovima"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Izvješće o programskoj pogrešci"</string>
<string name="bugreport_message" msgid="398447048750350456">"Time će se prikupiti podaci o trenutačnom stanju vašeg uređaja koje ćete nam poslati u e-poruci. Za pripremu izvješća o programskoj pogrešci potrebno je nešto vremena pa vas molimo za strpljenje."</string>
@@ -1356,7 +1355,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB pogon"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB pohrana"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Uredi"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Upozorenje o upotrebi podataka"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Upozorenje o upotrebi podataka"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Dodirnite za upotrebu i postavke"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Dost. ogr. 2G–3G prijenosa pod."</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Dost. ogr. 4G prijenosa podataka"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index f4ea98d..825672b 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefonbeállítások"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Képernyő lezárása"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Kikapcsolás"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Vészhívás"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Programhiba bejelentése"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Hibajelentés készítése"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ezzel információt fog gyűjteni az eszköz jelenlegi állapotáról, amelyet a rendszer e-mailben fog elküldeni. Kérjük, legyen türelemmel, amíg a hibajelentés elkészül, és küldhető állapotba kerül."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB-meghajtó"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-tár"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Szerkesztés"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Adathasználati figyelmeztetés"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Adathasználati értesítés"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Koppintson az adatokért."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-/3G-adatkorlát elérve"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G-adatkorlát elérve"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index f43dfb3..5a1ad70 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -251,17 +250,17 @@
<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="permgroupdesc_storage" msgid="637758554581589203">"օգտագործել լուսանկարները, մեդիա ֆայլերը և ձեր սարքում պահվող մյուս ֆայլերը"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Բարձրախոս"</string>
- <string name="permgroupdesc_microphone" msgid="4988812113943554584">"ձայնագրում"</string>
+ <string name="permgroupdesc_microphone" msgid="4988812113943554584">"ձայնագրել"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Ֆոտոխցիկ"</string>
- <string name="permgroupdesc_camera" msgid="3250611594678347720">"լուսանկարում և տեսագրում"</string>
+ <string name="permgroupdesc_camera" msgid="3250611594678347720">"լուսանկարել և տեսագրել"</string>
<string name="permgrouplab_phone" msgid="5229115638567440675">"Հեռախոս"</string>
- <string name="permgroupdesc_phone" msgid="6234224354060641055">"հեռախոսազանգերի կատարում և կառավարում"</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>
@@ -675,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>
@@ -1059,8 +1058,8 @@
<string name="volume_icon_description_incall" msgid="8890073218154543397">"Զանգի ձայնի բարձրություն"</string>
<string name="volume_icon_description_media" msgid="4217311719665194215">"Մեդիա ձայնի բարձրություն"</string>
<string name="volume_icon_description_notification" msgid="7044986546477282274">"Ծանուցումների ձայնի ուժգնությունը"</string>
- <string name="ringtone_default" msgid="3789758980357696936">"Լռելյայն զանգերանգ"</string>
- <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Լռելյայն զանգերանգ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default" msgid="3789758980357696936">"Կանխադրված զանգերանգ"</string>
+ <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Կանխադրված զանգերանգ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="7937634392408977062">"Ոչ մեկը"</string>
<string name="ringtone_picker_title" msgid="3515143939175119094">"Զանգերանգներ"</string>
<string name="ringtone_unknown" msgid="5477919988701784788">"Անհայտ զանգերանգ"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB սարքավար <xliff:g id="MANUFACTURER">%s</xliff:g>-ից"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB կրիչ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Խմբագրել"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Տվյալների օգտագործման նախազգուշացում"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Տվյալների օգտագործման զգուշացում"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Հպեք և տեսեք օգտագործումը և կարգավորումները:"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G տվյալների սահմանաչափը սպառվել է"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G տվյալների սահմանաչափը սպառվել է"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 2cbb6fb..05fe8d0 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opsi telepon"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Kunci layar"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Matikan daya"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Darurat"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Laporan bug"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Ambil laporan bug"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ini akan mengumpulkan informasi status perangkat Anda saat ini, untuk dikirimkan sebagai pesan email. Harap bersabar, mungkin perlu waktu untuk memulai laporan bug hingga siap dikirim."</string>
@@ -255,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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Drive USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Penyimpanan USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Peringatan penggunaan data"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Lansiran penggunaan data"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Ketuk untuk lihat penggunaan & setelan."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Batas data 2G-3G terlampaui"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Batas data 4G terlampaui"</string>
diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml
index ffd1e31..cd41f51 100644
--- a/core/res/res/values-is-rIS/strings.xml
+++ b/core/res/res/values-is-rIS/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Valkostir síma"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Skjálás"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Slökkva"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Neyðarsímtal"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Villutilkynning"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Útbúa villutilkynningu"</string>
<string name="bugreport_message" msgid="398447048750350456">"Þetta safnar upplýsingum um núverandi stöðu tækisins til að senda með tölvupósti. Það tekur smástund frá því villutilkynningin er ræst og þar til hún er tilbúin til sendingar – sýndu biðlund."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-drif frá <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-geymsla"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Breyta"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Viðvörun vegna gagnanotkunar"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Viðvörun um gagnanotkun"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Ýttu fyrir uppl. og stillingar"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Gagnahámarki 2G og 3G náð"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Gagnahámarki 4G náð"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index e576d8d..ec7f8f9 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opzioni telefono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Blocco schermo"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Spegni"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergenza"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Segnalazione di bug"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Apri segnalazione bug"</string>
<string name="bugreport_message" msgid="398447048750350456">"Verranno raccolte informazioni sullo stato corrente del dispositivo che saranno inviate sotto forma di messaggio email. Passerà un po\' di tempo prima che la segnalazione di bug aperta sia pronta per essere inviata; ti preghiamo di avere pazienza."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unità USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Archivio USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Modifica"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Avviso sull\'utilizzo dei dati"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Avviso sull\'utilizzo dei dati"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tocca per uso e impostazioni."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Limite di dati 2G-3G raggiunto"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Limite di dati 4G raggiunto"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index c0c1871..290a31f 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -218,8 +218,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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"כונן USB של <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"אחסון USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ערוך"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"אזהרת שימוש בנתונים"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"התראה לשימוש בנתונים"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"הקש כדי להציג נתוני שימוש והגדרות."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"הגעת למגבלת הנתונים של 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"הגעת למגבלת הנתונים של 4G"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 5924737..02736ec 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -513,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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g>製USBドライブ"</string>
<string name="storage_usb" msgid="3017954059538517278">"USBストレージ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"編集"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"データ使用の警告"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"データ使用量に関する通知"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"タップして使用状況と設定を表示します。"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G~3Gデータの上限に達しました"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4Gデータの上限に達しました"</string>
@@ -1608,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-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index b563591..1e34347 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB დისკი"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB მეხსიერება"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"რედაქტირება"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ინტერნეტის გამოყენების გაფრთხილება"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"მონაცემთა მოხმარების გაფრთხილება"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"შეეხეთ მოხმარებისა და პარამეტრების სანახავად."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G მონაცემთა ლიმიტი ამოიწურა"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G მონაცემთა ლიმიტი ამოიწურა"</string>
diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml
index e752aba..9a227c4 100644
--- a/core/res/res/values-kk-rKZ/strings.xml
+++ b/core/res/res/values-kk-rKZ/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB дискі"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB жады"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Өзгерту"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Дерекқор қолдануға қатысты ескерту"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Деректер трафигі туралы ескерту"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Трафик пен параметрлерді көру үшін түртіңіз."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G деректер шегіне жеттіңіз"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G деректер шегіне жеттіңіз"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 3fcf41c..2491f82 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1332,7 +1331,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"ឧបករណ៍ផ្ទុក USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"ឧបករណ៍ផ្ទុកយូអេសប៊ី"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"កែសម្រួល"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ការព្រមានប្រើទិន្នន័យ"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ការព្រមានអំពីការប្រើទិន្នន័យ"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"ប៉ះដើម្បីមើលការប្រើប្រាស់ និងការកំណត់"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"បានដល់ដែនកំណត់ទិន្នន័យ 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"បានដល់ដែនកំណត់ទិន្នន័យ 4G"</string>
diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml
index cd3f948..732a321 100644
--- a/core/res/res/values-kn-rIN/strings.xml
+++ b/core/res/res/values-kn-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -816,9 +815,9 @@
<string name="menu_space_shortcut_label" msgid="2410328639272162537">"space"</string>
<string name="menu_enter_shortcut_label" msgid="2743362785111309668">"enter"</string>
<string name="menu_delete_shortcut_label" msgid="3658178007202748164">"ಅಳಿಸು"</string>
- <string name="search_go" msgid="8298016669822141719">"ಹುಡುಕು"</string>
+ <string name="search_go" msgid="8298016669822141719">"ಹುಡುಕಿ"</string>
<string name="search_hint" msgid="1733947260773056054">"ಹುಡುಕಿ…"</string>
- <string name="searchview_description_search" msgid="6749826639098512120">"ಹುಡುಕು"</string>
+ <string name="searchview_description_search" msgid="6749826639098512120">"ಹುಡುಕಿ"</string>
<string name="searchview_description_query" msgid="5911778593125355124">"ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ"</string>
<string name="searchview_description_clear" msgid="1330281990951833033">"ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು"</string>
<string name="searchview_description_submit" msgid="2688450133297983542">"ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು"</string>
@@ -959,9 +958,9 @@
<string name="app_running_notification_title" msgid="8718335121060787914">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಚಾಲನೆಯಲ್ಲಿದೆ"</string>
<string name="app_running_notification_text" msgid="1197581823314971177">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್ ನಿಲ್ಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="ok" msgid="5970060430562524910">"ಸರಿ"</string>
- <string name="cancel" msgid="6442560571259935130">"ರದ್ದುಮಾಡು"</string>
+ <string name="cancel" msgid="6442560571259935130">"ರದ್ದುಮಾಡಿ"</string>
<string name="yes" msgid="5362982303337969312">"ಸರಿ"</string>
- <string name="no" msgid="5141531044935541497">"ರದ್ದುಮಾಡು"</string>
+ <string name="no" msgid="5141531044935541497">"ರದ್ದುಮಾಡಿ"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"ಗಮನಿಸಿ"</string>
<string name="loading" msgid="7933681260296021180">"ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
<string name="capital_on" msgid="1544682755514494298">"ಆನ್ ಮಾಡು"</string>
@@ -1119,7 +1118,7 @@
<string name="sms_short_code_details" msgid="5873295990846059400">"ಇದು ನಿಮ್ಮ ಮೊಬೈಲ್ ಖಾತೆಯಲ್ಲಿ "<b>"ಶುಲ್ಕಗಳನ್ನು ವಿಧಿಸುವುದಕ್ಕೆ ಕಾರಣವಾಗಬಹುದು"</b>"."</string>
<string name="sms_premium_short_code_details" msgid="7869234868023975"><b>"ಇದು ನಿಮ್ಮ ಮೊಬೈಲ್ ಖಾತೆಯಲ್ಲಿ ಶುಲ್ಕಗಳನ್ನು ವಿಧಿಸುವುದಕ್ಕೆ ಕಾರಣವಾಗುತ್ತದೆ."</b></string>
<string name="sms_short_code_confirm_allow" msgid="4458878637111023413">"ಕಳುಹಿಸು"</string>
- <string name="sms_short_code_confirm_deny" msgid="2927389840209170706">"ರದ್ದುಮಾಡು"</string>
+ <string name="sms_short_code_confirm_deny" msgid="2927389840209170706">"ರದ್ದುಮಾಡಿ"</string>
<string name="sms_short_code_remember_choice" msgid="5289538592272218136">"ನನ್ನ ಆಯ್ಕೆಯನ್ನು ನೆನಪಿಡು"</string>
<string name="sms_short_code_remember_undo_instruction" msgid="4960944133052287484">"ನೀವು ಇದನ್ನು ನಂತರದಲ್ಲಿ ಸೆಟ್ಟಿಂಗ್ಗಳು > ಅಪ್ಲಿಕೇಶನ್ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು"</string>
<string name="sms_short_code_confirm_always_allow" msgid="3241181154869493368">"ಯಾವಾಗಲೂ ಅನುಮತಿಸು"</string>
@@ -1212,7 +1211,7 @@
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ಝೂಮ್ ನಿಯಂತ್ರಿಸಲು ಎರಡು ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="gadget_host_error_inflating" msgid="4882004314906466162">"ವಿಜೆಟ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ."</string>
<string name="ime_action_go" msgid="8320845651737369027">"ಹೋಗು"</string>
- <string name="ime_action_search" msgid="658110271822807811">"ಹುಡುಕು"</string>
+ <string name="ime_action_search" msgid="658110271822807811">"ಹುಡುಕಿ"</string>
<string name="ime_action_send" msgid="2316166556349314424">"ಕಳುಹಿಸು"</string>
<string name="ime_action_next" msgid="3138843904009813834">"ಮುಂದೆ"</string>
<string name="ime_action_done" msgid="8971516117910934605">"ಮುಗಿದಿದೆ"</string>
@@ -1270,8 +1269,8 @@
<string name="share" msgid="1778686618230011964">"ಹಂಚು"</string>
<string name="find" msgid="4808270900322985960">"ಹುಡುಕಿ"</string>
<string name="websearch" msgid="4337157977400211589">"ವೆಬ್ ಹುಡುಕಾಟ"</string>
- <string name="find_next" msgid="5742124618942193978">"ಮುಂದಿನದನ್ನು ಹುಡುಕು"</string>
- <string name="find_previous" msgid="2196723669388360506">"ಹಿಂದಿನದನ್ನು ಹುಡುಕು"</string>
+ <string name="find_next" msgid="5742124618942193978">"ಮುಂದಿನದನ್ನು ಹುಡುಕಿ"</string>
+ <string name="find_previous" msgid="2196723669388360506">"ಹಿಂದಿನದನ್ನು ಹುಡುಕಿ"</string>
<string name="gpsNotifTicker" msgid="5622683912616496172">"<xliff:g id="NAME">%s</xliff:g> ಅವರಿಂದ ಸ್ಥಾನ ವಿನಂತಿ"</string>
<string name="gpsNotifTitle" msgid="5446858717157416839">"ಸ್ಥಾನ ವಿನಂತಿ"</string>
<string name="gpsNotifMessage" msgid="1374718023224000702">"<xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>) ಅವರಿಂದ ವಿನಂತಿಸಲಾಗಿದೆ"</string>
@@ -1304,7 +1303,7 @@
<string name="date_picker_prev_month_button" msgid="2858244643992056505">"ಹಿಂದಿನ ತಿಂಗಳು"</string>
<string name="date_picker_next_month_button" msgid="5559507736887605055">"ಮುಂದಿನ ತಿಂಗಳು"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
- <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"ರದ್ದುಮಾಡು"</string>
+ <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"ರದ್ದುಮಾಡಿ"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"ಅಳಿಸು"</string>
<string name="keyboardview_keycode_done" msgid="1992571118466679775">"ಮುಗಿದಿದೆ"</string>
<string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"ಮೋಡ್ ಬದಲಾವಣೆ"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ಡ್ರೈವ್"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB ಸಂಗ್ರಹಣೆ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ಎಡಿಟ್"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ಡೇಟಾ ಬಳಕೆಯ ಎಚ್ಚರಿಕೆ"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ಡೇಟಾ ಬಳಕೆ ಎಚ್ಚರಿಕೆ"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"ಬಳಕೆ ಮತ್ತು ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ಡೇಟಾ ಮೀತಿಯನ್ನು ತಲುಪಿದೆ"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ಡೇಟಾ ಮೀತಿಯನ್ನು ತಲುಪಿದೆ"</string>
@@ -1651,7 +1650,7 @@
<string name="language_picker_section_suggested" msgid="8414489646861640885">"ಸೂಚಿತ ಭಾಷೆ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ಎಲ್ಲಾ ಭಾಷೆಗಳು"</string>
<string name="region_picker_section_all" msgid="8966316787153001779">"ಎಲ್ಲಾ ಪ್ರದೇಶಗಳು"</string>
- <string name="locale_search_menu" msgid="2560710726687249178">"ಹುಡುಕು"</string>
+ <string name="locale_search_menu" msgid="2560710726687249178">"ಹುಡುಕಿ"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"ಕೆಲಸದ ಮೋಡ್ ಆಫ್ ಆಗಿದೆ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಹಿನ್ನೆಲೆ ಸಿಂಕ್ ಮತ್ತು ಇತರ ಸಂಬಂಧಿತ ವೈಶಿಷ್ಟ್ಯಗಳು ಸೇರಿದಂತೆ ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಕಾರ್ಯನಿರ್ವಹಿಸಲು ಅನುಮತಿಸಿ."</string>
<string name="work_mode_turn_on" msgid="2062544985670564875">"ಆನ್ ಮಾಡು"</string>
@@ -1674,7 +1673,7 @@
<string name="demo_restarting_message" msgid="952118052531642451">"ಸಾಧನ ಮರುಹೊಂದಿಸಲಾಗುತ್ತಿದೆ..."</string>
<string name="demo_user_inactivity_timeout_title" msgid="6596109959002331334">"ಸಾಧನವನ್ನು ಮರುಹೊಂದಿಸುವುದೇ?"</string>
<string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ನೀವು ಯಾವುದೇ ಬದಲಾವಣೆಗಳನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ ಮತ್ತು <xliff:g id="TIMEOUT">%1$s</xliff:g> ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಡೆಮೋ ಮತ್ತೆ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ..."</string>
- <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ರದ್ದುಮಾಡು"</string>
+ <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ರದ್ದುಮಾಡಿ"</string>
<string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ಈಗಲೇ ಮರುಹೊಂದಿಸು"</string>
<string name="audit_safemode_notification" msgid="6416076898350685856">"ನಿರ್ಬಂಧಗಳು ಇಲ್ಲದೆಯೇ ಈ ಸಾಧನವನ್ನು ಬಳಸಲು ಫ್ಯಾಕ್ಟರಿ ಮರುಹೊಂದಿಸಿ"</string>
<string name="audit_safemode_notification_details" msgid="1860601176690176413">"ಇನ್ನಷ್ಟು ತಿಳಿಯಲು ಸ್ಪರ್ಶಿಸಿ."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 8de15a8..f0cfd0b 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -261,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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB 드라이브"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB 저장소"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"수정"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"데이터 사용 경고"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"데이터 사용 알림"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"사용량 및 설정을 보려면 탭하세요."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G 데이터 한도에 도달함"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G 데이터 한도에 도달함"</string>
diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml
index 2d155bf..7a48ae5 100644
--- a/core/res/res/values-ky-rKG/strings.xml
+++ b/core/res/res/values-ky-rKG/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB түзмөгү"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB эстутуму"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Өзгөртүү"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Дайындарды колдонуу боюнча эскрт"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Дайындарды колдонууну чектөө"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Колдонулушун жана жөндөөлөрүн көрүү үчүн таптаңыз."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G дайындар чегине жетти"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G дайындар чегине жетти"</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-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 0676d15..26c9053 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ດຣ້າຍ"</string>
<string name="storage_usb" msgid="3017954059538517278">"ບ່ອນຈັດເກັບຂໍ້ມູນ USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ແກ້ໄຂ"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ເຕືອນກ່ຽວກັບການນຳໃຊ້ຂໍ້ມູນ"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ແຈ້ງເຕືອນການໃຊ້ອິນເຕີເນັດ"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"ແຕະເພື່ອເບິ່ງການນຳໃຊ້ ແລະ ການຕັ້ງຄ່າ."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"ໃຊ້ຂໍ້ມູນ 2G-3G ຮອດຈຳນວນທີ່ຈຳກັດແລ້ວ"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"ໃຊ້ຂໍ້ມູນ 4G ຮອດຈຳນວນທີ່ຈຳກັດແລ້ວ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index f43e6e8..c7806f2 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -218,8 +218,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefono parinktys"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekrano užraktas"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Išjungiamas maitinimas"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Skambutis pagalbos numeriu"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Pranešimas apie riktą"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Pranešti apie riktą"</string>
<string name="bugreport_message" msgid="398447048750350456">"Bus surinkta informacija apie dabartinę įrenginio būseną ir išsiųsta el. pašto pranešimu. Šiek tiek užtruks, kol pranešimas apie riktą bus paruoštas siųsti; būkite kantrūs."</string>
@@ -1106,7 +1105,7 @@
<string name="volume_icon_description_media" msgid="4217311719665194215">"Medijos garsumas"</string>
<string name="volume_icon_description_notification" msgid="7044986546477282274">"Pranešimo apimtis"</string>
<string name="ringtone_default" msgid="3789758980357696936">"Numatytasis skambėjimo tonas"</string>
- <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Numatytasis skambėjimo tonas (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Numatytasis skambėjimo tonas („<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>“)"</string>
<string name="ringtone_silent" msgid="7937634392408977062">"Nėra"</string>
<string name="ringtone_picker_title" msgid="3515143939175119094">"Skambėjimo tonai"</string>
<string name="ringtone_unknown" msgid="5477919988701784788">"Nežinomas skambėjimo tonas"</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"„<xliff:g id="MANUFACTURER">%s</xliff:g>“ atmintukas"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB atmintis"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Redaguoti"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Įspėjimas dėl duomenų naudojimo"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Duomenų naudojimo įspėjimas"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Pal. ir perž. naud. i. bei nust."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Pasiektas 2G–3G duomenų apribojimas"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Pasiektas 4G duomenų apribojimas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 3aa408a..6ea7d6b 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -216,8 +216,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Tālruņa opcijas"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekrāna bloķētājs"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Strāvas padeve ir izslēgta."</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Ārkārtas"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Kļūdu ziņojums"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Kļūdu ziņojuma sagatavošana"</string>
<string name="bugreport_message" msgid="398447048750350456">"Veicot šo darbību, tiks apkopota informācija par jūsu ierīces pašreizējo stāvokli un nosūtīta e-pasta ziņojuma veidā. Kļūdu ziņojuma pabeigšanai un nosūtīšanai var būt nepieciešams laiks. Lūdzu, esiet pacietīgs."</string>
@@ -1356,7 +1355,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB disks"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB atmiņa"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Rediģēt"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Datu izmantošanas brīdinājums"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Brīdinājums par datu lietojumu"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Piesk., lai sk. lietoj. un iest."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Sasniegts 2G-3G datu ierobež."</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Sasniegts 4G datu ierobežojums"</string>
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index ddf0e9f..1d7a45b 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -32,10 +32,5 @@
-->
<integer name="config_LTE_RSRP_threshold_type">0</integer>
- <string-array translatable="false" name="config_sms_convert_destination_number_support">
- <item>true;BAE0000000000000</item>
- <item>false</item>
- </string-array>
-
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
</resources>
diff --git a/core/res/res/values-mcc259-mnc05/config.xml b/core/res/res/values-mcc259-mnc05/config.xml
deleted file mode 100644
index 065668c..0000000
--- a/core/res/res/values-mcc259-mnc05/config.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2013, 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- The list of ril radio technologies (see ServiceState.java) which only support
- a single data connection at one time. This may change by carrier via
- overlays (some don't support multiple pdp on UMTS). All unlisted radio
- tech types support unlimited types (practically only 2-4 used). -->
- <integer-array name="config_onlySingleDcAllowed">
- <item>1</item> <!-- GPRS -->
- <item>2</item> <!-- EDGE -->
- <item>3</item> <!-- UMTS -->
- <item>9</item> <!-- HSDPA -->
- <item>10</item> <!-- HSUPA -->
- <item>11</item> <!-- HSPA -->
- <item>14</item> <!-- LTE -->
- <item>15</item> <!-- HSPAP -->
- </integer-array>
-</resources>
diff --git a/core/res/res/values-mcc302-mnc220/config.xml b/core/res/res/values-mcc302-mnc220/config.xml
index d638b89..422f7c9 100644
--- a/core/res/res/values-mcc302-mnc220/config.xml
+++ b/core/res/res/values-mcc302-mnc220/config.xml
@@ -21,14 +21,6 @@
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string-array translatable="false" name="config_operatorConsideredNonRoaming">
- <item>302370</item>
- <item>302610</item>
- <item>302660</item>
- <item>302720</item>
- <item>302780</item>
- </string-array>
-
<integer name="config_mobile_mtu">1410</integer>
<!-- String containing the apn value for tethering. May be overriden by secure settings
diff --git a/core/res/res/values-mcc310-mnc004/config.xml b/core/res/res/values-mcc310-mnc004/config.xml
index cbe5145..63431a4 100755
--- a/core/res/res/values-mcc310-mnc004/config.xml
+++ b/core/res/res/values-mcc310-mnc004/config.xml
@@ -35,8 +35,5 @@
<bool name="config_auto_attach_data_on_creation">false</bool>
- <string-array translatable="false" name="config_sms_convert_destination_number_support">
- <item>true</item>
- </string-array>
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true</string>
</resources>
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index 714b312..a0a361b 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -48,9 +48,6 @@
provisioning, availability etc -->
<bool name="config_carrier_vt_available">true</bool>
- <!-- Flag specifying whether VoLTE availability is based on provisioning -->
- <bool name="config_carrier_volte_provisioned">true</bool>
-
<bool name="config_auto_attach_data_on_creation">false</bool>
<!-- Flag indicating whether strict threshold is used, or lenient threshold is used,
@@ -60,9 +57,6 @@
-->
<integer name="config_LTE_RSRP_threshold_type">0</integer>
- <string-array translatable="false" name="config_sms_convert_destination_number_support">
- <item>true</item>
- </string-array>
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true</string>
</resources>
diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml
index 437df10..3f228203 100644
--- a/core/res/res/values-mk-rMK/strings.xml
+++ b/core/res/res/values-mk-rMK/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1332,7 +1331,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> УСБ-меморија"</string>
<string name="storage_usb" msgid="3017954059538517278">"УСБ меморија"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Уреди"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Опомена за потрошен интернет"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Известување за потрошен сообраќај"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Допрете за употреба и поставки."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Постигна лимит за 2G-3G податоци"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Постигнат лимит за 4G податоци"</string>
diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml
index 76d05c7..beb6a67 100644
--- a/core/res/res/values-ml-rIN/strings.xml
+++ b/core/res/res/values-ml-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ഡ്രൈവ്"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB സ്റ്റോറേജ്"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"എഡിറ്റുചെയ്യുക"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ഡാറ്റ ഉപയോഗ മുന്നറിയിപ്പ്"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ഡാറ്റാ ഉപയോഗ മുന്നറിയിപ്പ്"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"ഉപയോഗവും ക്രമീകരണവും കാണാൻ ടാപ്പുചെയ്യുക."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ഡാറ്റ പരിധിയിലെത്തി"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ഡാറ്റ പരിധിയിലെത്തി"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 074e5c3..bd80f84 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB диск"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB сан"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Засах"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Дата хэрэглээний анхааруулга"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Дата ашиглалтын сануулга"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Хэрэглээ, тохиргоог харах бол товш."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G дата хязгаарт хүрсэн"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G дата хязгаарт хүрсэн"</string>
diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml
index 1997714..758b21b 100644
--- a/core/res/res/values-mr-rIN/strings.xml
+++ b/core/res/res/values-mr-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1076,7 +1075,7 @@
<string name="network_available_sign_in" msgid="1848877297365446605">"नेटवर्कवर साइन इन करा"</string>
<!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
<skip />
- <string name="wifi_no_internet" msgid="8451173622563841546">"वाय-फाय मध्ये इंटरनेट प्रवेश नाही"</string>
+ <string name="wifi_no_internet" msgid="8451173622563841546">"वाय-फायवरून इंटरनेटवर प्रवेश नाही"</string>
<string name="wifi_no_internet_detailed" msgid="8083079241212301741">"पर्यायांसाठी टॅप करा"</string>
<string name="network_switch_metered" msgid="4671730921726992671">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> वर स्विच केले"</string>
<string name="network_switch_metered_detail" msgid="5325661434777870353">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> कडे इंटरनेट प्रवेश नसताना डिव्हाइस <xliff:g id="NEW_NETWORK">%1$s</xliff:g> वापरतो. शुल्क लागू शकतील."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ड्राइव्ह"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB संचयन"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"संपादित करा"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"डेटा वापर चेतावणी"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"डेटा वापर सूचना"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"वापर आणि सेटिंग्ज पाहण्यासाठी टॅप करा."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G डेटा मर्यादा गाठली"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G डेटा मर्यादा गाठली"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index 0846ede..60330e5 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Pilihan telefon"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Kunci skrin"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Matikan kuasa"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Kecemasan"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Laporan pepijat"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Ambil laporan pepijat"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ini akan mengumpul maklumat tentang keadaan peranti semasa anda untuk dihantarkan sebagai mesej e-mel. Harap bersabar, mungkin perlu sedikit masa untuk memulakan laporan sehingga siap untuk dihantar."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Pemacu USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Storan USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Amaran penggunaan data"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Makluman penggunaan data"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Ketik utk lihat p\'gunaan & ttpn."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Mencapai had data 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Mencapai had data 4G"</string>
diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml
index 549bb69..c18ff7f 100644
--- a/core/res/res/values-my-rMM/strings.xml
+++ b/core/res/res/values-my-rMM/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ဒရိုက်ဗ်"</string>
<string name="storage_usb" msgid="3017954059538517278">"USBဖြင့် သိမ်းဆည်း"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ပြင်ဆင်ရန်"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ဒေတာအသုံးပြုမှုသတိပေးချက်"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ဒေတာအသုံးပြုမှုသတိပေးချက်"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"အသုံးပြုမှုနှင့် ဆက်တင်များကိုကြည့်ရန် တို့ပါ။"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ဒေတာ ကန့်သတ်ချက် ပြည့်မီသွားပြီ"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ဒေတာ ကန့်သတ်ချက် ပြည့်မီသွားပြီ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 33f1436..0371543 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefoninnstillinger"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Lås skjermen"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Slå av"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Nødssituasjon"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Feilrapport"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Utfør feilrapport"</string>
<string name="bugreport_message" msgid="398447048750350456">"Informasjon om tilstanden til enheten din samles inn og sendes som en e-post. Det tar litt tid fra du starter feilrapporten til e-posten er klar, så vær tålmodig."</string>
@@ -1329,8 +1328,8 @@
<string name="storage_usb_drive" msgid="6261899683292244209">"USB-stasjon"</string>
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB-stasjon"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-lagring"</string>
- <string name="extract_edit_menu_button" msgid="8940478730496610137">"Rediger"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Advarsel for høyt dataforbruk"</string>
+ <string name="extract_edit_menu_button" msgid="8940478730496610137">"Endre"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Varsel om databruk"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Trykk for å se bruken og innstillingene."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Datagrensen for 2G-3G er nådd"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Datagrensen for 4G er nådd"</string>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index d4ad51f..e039927 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -257,7 +256,7 @@
<string name="permgrouplab_microphone" msgid="171539900250043464">"माइक्रोफोन"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"अडियो रेकर्ड गर्नुहोस्"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"क्यामेरा"</string>
- <string name="permgroupdesc_camera" msgid="3250611594678347720">"तस्बिर खिच्नुहोस् तथा भिडियो रेकर्ड गर्नुहोस्"</string>
+ <string name="permgroupdesc_camera" msgid="3250611594678347720">"तस्बिर खिच्नुका साथै भिडियो रेकर्ड गर्नुहोस्"</string>
<string name="permgrouplab_phone" msgid="5229115638567440675">"फोन"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"फोन कलहरू गर्नुहोस् र व्यवस्थापन गर्नुहोस्"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"शारीरिक सेन्सर"</string>
@@ -1336,7 +1335,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ड्राइभ"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB भण्डारण"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"सम्पादन गर्नुहोस्"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"डेटाको प्रयोग चेतावनी"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"डेटा प्रयोग बारे सतर्कता"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"प्रयोग र सेटिङहरू हेर्न ट्याप गर्नुहोस्।"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G डेटा सीमा पुग्यो"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G डेटा सीमा पुग्यो"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 33fcb8a..13218b7 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefoonopties"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Schermvergrendeling"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Uitschakelen"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Noodgeval"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Foutenrapport"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Foutenrapport genereren"</string>
<string name="bugreport_message" msgid="398447048750350456">"Hiermee worden gegevens over de huidige status van je apparaat verzameld en als e-mail verzonden. Wanneer u een foutenrapport start, duurt het even voordat het kan worden verzonden. Even geduld alstublieft."</string>
@@ -238,7 +237,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Spraakassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Nu vergrendelen"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
- <string name="notification_hidden_text" msgid="1135169301897151909">"Inhoud verborgen"</string>
+ <string name="notification_hidden_text" msgid="1135169301897151909">"Content verborgen"</string>
<string name="notification_hidden_by_policy_text" msgid="9004631276932584600">"Content verborgen op basis van beleid"</string>
<string name="safeMode" msgid="2788228061547930246">"Veilige modus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-systeem"</string>
@@ -262,12 +261,12 @@
<string name="permgroupdesc_phone" msgid="6234224354060641055">"bellen en telefoontjes beheren"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"Lichaamssensoren"</string>
<string name="permgroupdesc_sensors" msgid="7147968539346634043">"toegang krijgen tot sensorgegevens over je vitale functies"</string>
- <string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"Inhoud van vensters ophalen"</string>
- <string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"De inhoud inspecteren van een venster waarmee je interactie hebt."</string>
+ <string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"Content van vensters ophalen"</string>
+ <string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"De content inspecteren van een venster waarmee je interactie hebt."</string>
<string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"\'Verkennen via aanraking\' inschakelen"</string>
<string name="capability_desc_canRequestTouchExploration" msgid="7543249041581408313">"Aangetikte items worden hardop benoemd en het scherm kan worden verkend door middel van gebaren."</string>
<string name="capability_title_canRequestEnhancedWebAccessibility" msgid="1739881766522594073">"Verbeterde internettoegankelijkheid inschakelen"</string>
- <string name="capability_desc_canRequestEnhancedWebAccessibility" msgid="7881063961507511765">"Er kunnen scripts worden geïnstalleerd om app-inhoud toegankelijker te maken."</string>
+ <string name="capability_desc_canRequestEnhancedWebAccessibility" msgid="7881063961507511765">"Er kunnen scripts worden geïnstalleerd om app-content toegankelijker te maken."</string>
<string name="capability_title_canRequestFilterKeyEvents" msgid="2103440391902412174">"Tekst observeren die u typt"</string>
<string name="capability_desc_canRequestFilterKeyEvents" msgid="7463135292204152818">"Omvat persoonlijke gegevens zoals creditcardnummers en wachtwoorden."</string>
<string name="capability_title_canControlMagnification" msgid="3593493281059424855">"Schermvergroting bedienen"</string>
@@ -297,9 +296,9 @@
<string name="permlab_sendSms" msgid="7544599214260982981">"sms\'jes verzenden en bekijken"</string>
<string name="permdesc_sendSms" msgid="7094729298204937667">"Hiermee kan de app sms-berichten verzenden. Dit kan tot onverwachte kosten leiden. Schadelijke apps kunnen u geld kosten doordat ze zonder je bevestiging berichten kunnen verzenden."</string>
<string name="permlab_readSms" msgid="8745086572213270480">"je tekstberichten (SMS of MMS) lezen"</string>
- <string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Hiermee kan de app sms-berichten lezen die zijn opgeslagen op je tablet of simkaart. De app kan alle sms-berichten lezen, ongeacht inhoud of vertrouwelijkheid."</string>
- <string name="permdesc_readSms" product="tv" msgid="5102425513647038535">"Hiermee kan de app sms-berichten lezen die zijn opgeslagen op je tv of simkaart. De app kan alle sms-berichten lezen, ongeacht inhoud of vertrouwelijkheid."</string>
- <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Hiermee kan de app sms-berichten lezen die zijn opgeslagen op je telefoon of simkaart. De app kan alle sms-berichten lezen, ongeacht inhoud of vertrouwelijkheid."</string>
+ <string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Hiermee kan de app sms-berichten lezen die zijn opgeslagen op je tablet of simkaart. De app kan alle sms-berichten lezen, ongeacht content of vertrouwelijkheid."</string>
+ <string name="permdesc_readSms" product="tv" msgid="5102425513647038535">"Hiermee kan de app sms-berichten lezen die zijn opgeslagen op je tv of simkaart. De app kan alle sms-berichten lezen, ongeacht content of vertrouwelijkheid."</string>
+ <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Hiermee kan de app sms-berichten lezen die zijn opgeslagen op je telefoon of simkaart. De app kan alle sms-berichten lezen, ongeacht content of vertrouwelijkheid."</string>
<string name="permlab_receiveWapPush" msgid="5991398711936590410">"tekstberichten (WAP) ontvangen"</string>
<string name="permdesc_receiveWapPush" msgid="748232190220583385">"Hiermee kan de app WAP-berichten ontvangen en verwerken. Dit betekent dat de app berichten die naar je apparaat zijn verzonden, kan bijhouden of verwijderen zonder deze aan u weer te geven."</string>
<string name="permlab_getTasks" msgid="6466095396623933906">"actieve apps ophalen"</string>
@@ -461,12 +460,12 @@
<string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"Hiermee kan een app de synchronisatie-instellingen aanpassen voor een account. Deze toestemming kan bijvoorbeeld worden gebruikt om synchronisatie van de app Personen in te schakelen voor een account."</string>
<string name="permlab_readSyncStats" msgid="7396577451360202448">"synchronisatiestatistieken lezen"</string>
<string name="permdesc_readSyncStats" msgid="1510143761757606156">"Hiermee kan een app de synchronisatiestatistieken voor een account lezen, inclusief de geschiedenis van synchronisatie-activiteiten en hoeveel gegevens zijn gesynchroniseerd."</string>
- <string name="permlab_sdcardRead" product="nosdcard" msgid="367275095159405468">"de inhoud van je USB-opslag lezen"</string>
- <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"de inhoud van je SD-kaart lezen"</string>
- <string name="permdesc_sdcardRead" product="nosdcard" msgid="3446988712598386079">"De app toestaan de inhoud van je USB-opslag te lezen."</string>
- <string name="permdesc_sdcardRead" product="default" msgid="2607362473654975411">"De app toestaan de inhoud van je SD-kaart te lezen."</string>
- <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"de inhoud van je USB-opslag aanpassen of verwijderen"</string>
- <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"de inhoud van je SD-kaart aanpassen of verwijderen"</string>
+ <string name="permlab_sdcardRead" product="nosdcard" msgid="367275095159405468">"de content van je USB-opslag lezen"</string>
+ <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"de content van je SD-kaart lezen"</string>
+ <string name="permdesc_sdcardRead" product="nosdcard" msgid="3446988712598386079">"De app toestaan de content van je USB-opslag te lezen."</string>
+ <string name="permdesc_sdcardRead" product="default" msgid="2607362473654975411">"De app toestaan de content van je SD-kaart te lezen."</string>
+ <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"de content van je USB-opslag aanpassen of verwijderen"</string>
+ <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"de content van je SD-kaart aanpassen of verwijderen"</string>
<string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"Hiermee kan de app schrijven naar de USB-opslag."</string>
<string name="permdesc_sdcardWrite" product="default" msgid="4337417790936632090">"Hiermee kan de app schrijven naar de SD-kaart."</string>
<string name="permlab_use_sip" msgid="2052499390128979920">"SIP-oproepen plaatsen/ontvangen"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB-drive"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-opslag"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Bewerken"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Waarschuwing v. gegevensgebruik"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Melding voor datagebruik"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Tik voor gebruik en instellingen"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Gegevenslimiet van 2G-3G bereikt"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Gegevenslimiet van 4G bereikt"</string>
@@ -1528,7 +1527,7 @@
<string name="mediasize_unknown_portrait" msgid="3088043641616409762">"Onbekend portret"</string>
<string name="mediasize_unknown_landscape" msgid="4876995327029361552">"Onbekend landschap"</string>
<string name="write_fail_reason_cancelled" msgid="7091258378121627624">"Geannuleerd"</string>
- <string name="write_fail_reason_cannot_write" msgid="8132505417935337724">"Fout bij schrijven van inhoud"</string>
+ <string name="write_fail_reason_cannot_write" msgid="8132505417935337724">"Fout bij schrijven van content"</string>
<string name="reason_unknown" msgid="6048913880184628119">"onbekend"</string>
<string name="reason_service_unavailable" msgid="7824008732243903268">"Afdrukservice niet ingeschakeld"</string>
<string name="print_service_installed_title" msgid="2246317169444081628">"<xliff:g id="NAME">%s</xliff:g>-service geïnstalleerd"</string>
diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml
index ae8a4b1..d3476aa 100644
--- a/core/res/res/values-pa-rIN/strings.xml
+++ b/core/res/res/values-pa-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -364,7 +363,7 @@
<string name="permdesc_accessCoarseLocation" msgid="2538200184373302295">"ਐਪ ਨੂੰ ਤੁਹਾਡਾ ਅਨੁਮਾਨਿਤ ਨਿਰਧਾਰਿਤ ਸਥਾਨ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਨਿਰਧਾਰਿਤ ਸਥਾਨ ਨੈੱਟਵਰਕ ਨਿਰਧਾਰਿਤ ਸਥਾਨ ਸਰੋਤ ਵਰਤਦੇ ਹੋਏ ਨਿਰਧਾਰਿਤ ਸਥਾਨ ਸੇਵਾਵਾਂ ਰਾਹੀਂ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਜਿਵੇਂ ਸੈਲ ਟਾਵਰ ਅਤੇ Wi-Fi. ਇਹ ਨਿਰਧਾਰਿਤ ਸਥਾਨ ਸੇਵਾਵਾਂ ਚਾਲੂ ਅਤੇ ਐਪ ਨੂੰ ਉਹਨਾਂ ਨੂੰ ਵਰਤਣ ਲਈ ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਤੇ ਹੋਣੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ। ਐਪਸ ਇਸਦੀ ਵਰਤੋਂ ਇਹ ਅਨੁਮਾਨ ਲਗਾਉਣ ਲਈ ਕਰ ਸਕਦੇ ਹਨ ਕਿ ਤੁਸੀਂ ਕਿੱਥੇ ਹੋ।"</string>
<string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"ਆਪਣੀਆਂ ਔਡੀਓ ਸੈਟਿੰਗਾਂ ਬਦਲੋ"</string>
<string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ਔਪ ਨੂੰ ਗਲੋਬਲ ਔਡੀਓ ਸੈਟਿੰਗਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ ਜਿਵੇਂ ਵੌਲਿਊਮ ਅਤੇ ਆਊਟਪੁਟ ਲਈ ਕਿਹੜਾ ਸਪੀਕਰ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ।"</string>
- <string name="permlab_recordAudio" msgid="3876049771427466323">"ਔਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="permlab_recordAudio" msgid="3876049771427466323">"ਔਡੀਓ ਰਿਕਾਰਡ ਕਰਨ"</string>
<string name="permdesc_recordAudio" msgid="4906839301087980680">"ਐਪ ਨੂੰ ਮਾਈਕ੍ਰੋਫੋਨ ਨਾਲ ਔਡੀਓ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਅਨੁਮਤੀ ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਪੁਸ਼ਟੀ ਤੋਂ ਬਿਨਾਂ ਕਿਸੇ ਵੀ ਸਮੇਂ ਔਡੀਓ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
<string name="permlab_sim_communication" msgid="2935852302216852065">"SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜੋ"</string>
<string name="permdesc_sim_communication" msgid="5725159654279639498">"ਐਪ ਨੂੰ SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਬਹੁਤ ਘਾਤਕ ਹੈ।"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ਡ੍ਰਾਇਵ"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB ਸਟੋਰੇਜ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ਸੰਪਾਦਿਤ ਕਰੋ"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ਡੈਟਾ ਉਪਯੋਗ ਚਿਤਾਵਨੀ"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ਡੈਟਾ ਵਰਤੋਂ ਚੇਤਾਵਨੀ"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"ਵਰਤੋਂ ਅਤੇ ਸੈਟਿੰਗਾਂ ਨੂੰ ਵੇਖਣ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ਡੈਟਾ ਸੀਮਾ ਪੂਰੀ ਹੋ ਗਈ"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ਡੈਟਾ ਸੀਮਾ ਪੂਰੀ ਹੋਈ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 8a2dae5..a7571ef 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -218,8 +218,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opcje telefonu"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Blokada ekranu"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Wyłącz"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Alarmowy"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Zgłoszenie błędu"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Zgłoś błąd"</string>
<string name="bugreport_message" msgid="398447048750350456">"Informacje o bieżącym stanie urządzenia zostaną zebrane i wysłane e-mailem. Przygotowanie zgłoszenia błędu do wysłania chwilę potrwa, więc zachowaj cierpliwość."</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Dysk USB (<xliff:g id="MANUFACTURER">%s</xliff:g>)"</string>
<string name="storage_usb" msgid="3017954059538517278">"Nośnik USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edytuj"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Ostrzeżenie o transmisji danych"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alert transmisji danych"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Kliknij, by wyświetlić użycie i ustawienia."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Osiągnięto limit danych 2G/3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Osiągnięto limit danych 4G"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 191a41a..24265ee 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opções do telefone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloquear tela"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Desligar"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergência"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Relatório de bugs"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Obter relatório de bugs"</string>
<string name="bugreport_message" msgid="398447048750350456">"Isto coletará informações sobre o estado atual do dispositivo para enviá-las em uma mensagem de e-mail. Após iniciar o relatório de bugs, será necessário aguardar algum tempo até que esteja pronto para ser enviado."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Drive USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Armazenamento USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editar"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Aviso sobre uso de dados"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta de uso de dados"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Toque para ver uso e config."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Limite de dados 2G-3G atingido"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Limite de dados 4G atingido"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 9db6a76..6270210 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opções do telefone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueio de ecrã"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Desligar"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergência"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Relatório de erros"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Criar relatório de erros"</string>
<string name="bugreport_message" msgid="398447048750350456">"Será recolhida informação sobre o estado atual do seu dispositivo a enviar através de uma mensagem de email. Demorará algum tempo até que o relatório de erro esteja pronto para ser enviado. Aguarde um pouco."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unidade USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Armazenamento USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editar"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Aviso de utilização de dados"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta de utilização de dados"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Toque para ver a utilização e definições"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Limite de dados 2G/3G atingido"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Limite de dados 4G atingido"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 191a41a..24265ee 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opções do telefone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloquear tela"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Desligar"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergência"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Relatório de bugs"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Obter relatório de bugs"</string>
<string name="bugreport_message" msgid="398447048750350456">"Isto coletará informações sobre o estado atual do dispositivo para enviá-las em uma mensagem de e-mail. Após iniciar o relatório de bugs, será necessário aguardar algum tempo até que esteja pronto para ser enviado."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Drive USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Armazenamento USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editar"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Aviso sobre uso de dados"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerta de uso de dados"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Toque para ver uso e config."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Limite de dados 2G-3G atingido"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Limite de dados 4G atingido"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 197da96..29c0a59 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -216,8 +216,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opțiuni telefon"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Blocați ecranul"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Opriți alimentarea"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Urgență"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Raport despre erori"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Executați un raport despre erori"</string>
<string name="bugreport_message" msgid="398447048750350456">"Acest raport va colecta informații despre starea actuală a dispozitivului, pentru a le trimite într-un e-mail. Aveți răbdare după pornirea raportului despre erori până când va fi gata de trimis."</string>
@@ -1356,7 +1355,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Unitate USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Dsipozitiv de stocare USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Editați"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Avertisment de utiliz. a datelor"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alertă pentru utilizarea datelor"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Atingeți ca să vedeți utilizarea/setările."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Ați atins limita de date 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Ați atins limita de date 4G"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 11f19b8..ed6c798 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -218,8 +218,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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-накопитель <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-накопитель"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Изменить"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Осталось мало трафика"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Лимит на передачу данных"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Нажмите, чтобы проверить трафик и настройки."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Достигнут лимит трафика 2G/3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Достигнут лимит трафика 4G"</string>
diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml
index 73e28dc..91d5bbe 100644
--- a/core/res/res/values-si-rLK/strings.xml
+++ b/core/res/res/values-si-rLK/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1332,7 +1331,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ධාවකය"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB ආචයනය"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"සංස්කරණය කරන්න"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"දත්ත භාවිතා අවවාදය"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"දත්ත භාවිතය ගැන ඇඟවීම"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"භාවිතය සහ සැකසීම් බැලීමට තට්ටු කරන්න."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G දත්ත සීමාවට ළඟාවී ඇත"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G දත්ත සීමාවට ළඟාවී ඇත"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 2aa140a..5c129af 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -218,8 +218,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Možnosti telefónu"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zámka obrazovky"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Vypnúť"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Tiesňové volanie"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Hlásenie o chybách"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Vytvoriť hlásenie chyby"</string>
<string name="bugreport_message" msgid="398447048750350456">"Týmto zhromaždíte informácie o aktuálnom stave zariadenia. Informácie je potom možné odoslať e-mailom, chvíľu však potrvá, kým bude hlásenie chyby pripravené na odoslanie. Prosíme vás preto o trpezlivosť."</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Disk USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Ukladací priestor USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Upraviť"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Upozornenie o využití dát"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Upozornenie na spotrebu dát"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Klepnutím zobrazíte využitie a nastavenia."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Bol dosiahnutý limit 2G–3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Bol dosiahnutý limit 4G"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index d942c46..76f1e66 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -218,8 +218,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Možnosti telefona"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zaklep zaslona"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Izklopi"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Klic v sili"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Poročilo o napakah"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Ustvari poročilo o napakah"</string>
<string name="bugreport_message" msgid="398447048750350456">"S tem bodo zbrani podatki o trenutnem stanju naprave, ki bodo poslani v e-poštnem sporočilu. Izvedba poročila o napakah in priprava trajata nekaj časa, zato bodite potrpežljivi."</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Pogon USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Pomnilnik USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Uredi"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Opozorilo o uporabi podatkov"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Opozorilo o preneseni količini podatkov"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Dot. se za ogled upor. in nast."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Dosežena pod. omejitev za 2G/3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Dosežena pod. omejitev za 4G"</string>
diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml
index 26637b4..0fb3ebe 100644
--- a/core/res/res/values-sq-rAL/strings.xml
+++ b/core/res/res/values-sq-rAL/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Opsionet e telefonit"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Kyçja e ekranit"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Fik"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Urgjenca"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Raporti i defekteve në kod"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Merr raportin e defekteve në kod"</string>
<string name="bugreport_message" msgid="398447048750350456">"Ky funksion mundëson mbledhjen e informacioneve mbi gjendjen aktuale të pajisjes për ta dërguar si mesazh mail-i. Do të duhet pak kohë nga nisja e raportit të defekteve në kod. Faleminderit për durimin."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-ja nga <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Hapësira ruajtëse e USB-së"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Redakto"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Paralajmërim për përdorimin e të dhënave"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Sinjalizimi i të dhënave"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Trokit për të parë përdorimin dhe cilësimet."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Kufiri i të dhënave 2G-3G u arrit"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Kufiri i të dhënave 4G u arrit"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 162debe..fdf8a6d 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -152,7 +152,7 @@
<string name="httpErrorAuth" msgid="1435065629438044534">"Није могуће потврдити аутентичност."</string>
<string name="httpErrorProxyAuth" msgid="1788207010559081331">"Потврда идентитета преко прокси сервера није успела."</string>
<string name="httpErrorConnect" msgid="8714273236364640549">"Није могуће повезати се са сервером."</string>
- <string name="httpErrorIO" msgid="2340558197489302188">"Није могуће комуницирати са сервером. Покушајте поново касније."</string>
+ <string name="httpErrorIO" msgid="2340558197489302188">"Није могуће комуницирати са сервером. Пробајте поново касније."</string>
<string name="httpErrorTimeout" msgid="4743403703762883954">"Веза са сервером је истекла."</string>
<string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Страница садржи превише веза за преусмеравање са сервера."</string>
<string name="httpErrorUnsupportedScheme" msgid="5015730812906192208">"Протокол није подржан."</string>
@@ -160,7 +160,7 @@
<string name="httpErrorBadUrl" msgid="3636929722728881972">"Страницу није могуће отворити зато што је URL адреса неважећа."</string>
<string name="httpErrorFile" msgid="2170788515052558676">"Није могуће приступити датотеци."</string>
<string name="httpErrorFileNotFound" msgid="6203856612042655084">"Није могуће пронаћи тражену датотеку."</string>
- <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Превише захтева се обрађује. Покушајте поново касније."</string>
+ <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Превише захтева се обрађује. Пробајте поново касније."</string>
<string name="notification_title" msgid="8967710025036163822">"Грешка при пријављивању за <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="8353523060269335667">"Синхронизација"</string>
<string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Синхронизација"</string>
@@ -216,8 +216,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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -441,19 +440,19 @@
<string name="permdesc_manageFingerprint" msgid="178208705828055464">"Дозвољава апликацији да активира методе за додавање и брисање шаблона отисака прстију који ће се користити."</string>
<string name="permlab_useFingerprint" msgid="3150478619915124905">"користи хардвер за отиске прстију"</string>
<string name="permdesc_useFingerprint" msgid="9165097460730684114">"Дозвољава апликацији да користи хардвер за отиске прстију ради потврде аутентичности"</string>
- <string name="fingerprint_acquired_partial" msgid="735082772341716043">"Откривен је делимични отисак прста. Покушајте поново."</string>
- <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Није успела обрада отиска прста. Покушајте поново."</string>
+ <string name="fingerprint_acquired_partial" msgid="735082772341716043">"Откривен је делимични отисак прста. Пробајте поново."</string>
+ <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Није успела обрада отиска прста. Пробајте поново."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"Сензор за отиске прстију је прљав. Очистите га и покушајте поново."</string>
- <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Пребрзо сте померили прст. Покушајте поново."</string>
- <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"Превише споро сте померили прст. Покушајте поново."</string>
+ <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Пребрзо сте померили прст. Пробајте поново."</string>
+ <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"Превише споро сте померили прст. Пробајте поново."</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
<string name="fingerprint_error_hw_not_available" msgid="7955921658939936596">"Хардвер за отиске прстију није доступан."</string>
<string name="fingerprint_error_no_space" msgid="1055819001126053318">"Није могуће сачувати отисак прста. Уклоните неки од постојећих отисака прстију."</string>
- <string name="fingerprint_error_timeout" msgid="3927186043737732875">"Временско ограничење за отисак прста је истекло. Покушајте поново."</string>
+ <string name="fingerprint_error_timeout" msgid="3927186043737732875">"Временско ограничење за отисак прста је истекло. Пробајте поново."</string>
<string name="fingerprint_error_canceled" msgid="4402024612660774395">"Радња са отиском прста је отказана."</string>
- <string name="fingerprint_error_lockout" msgid="5536934748136933450">"Превише покушаја. Покушајте поново касније."</string>
- <string name="fingerprint_error_unable_to_process" msgid="6107816084103552441">"Покушајте поново."</string>
+ <string name="fingerprint_error_lockout" msgid="5536934748136933450">"Превише покушаја. Пробајте поново касније."</string>
+ <string name="fingerprint_error_unable_to_process" msgid="6107816084103552441">"Пробајте поново."</string>
<string name="fingerprint_name_template" msgid="5870957565512716938">"Прст <xliff:g id="FINGERID">%d</xliff:g>"</string>
<string-array name="fingerprint_error_vendor">
</string-array>
@@ -681,8 +680,8 @@
<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>
- <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Покушајте поново"</string>
+ <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Пробајте поново"</string>
+ <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Пробајте поново"</string>
<string name="lockscreen_storage_locked" msgid="9167551160010625200">"Откључај за све функције и податке"</string>
<string name="faceunlock_multiple_failures" msgid="754137583022792429">"Премашен је највећи дозвољени број покушаја Откључавања лицем"</string>
<string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"Нема SIM картице"</string>
@@ -706,19 +705,19 @@
<string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"Погледајте Кориснички водич или контактирајте Корисничку подршку."</string>
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM картица је закључана."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Откључавање SIM картице…"</string>
- <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте неправилно нацртали шаблон за откључавање. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте погрешно унели лозинку. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте погрешно унели PIN. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте нетачно унели шаблон за откључавање. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> несупешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу података за пријављивање на Google.\n\n Покушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"Неисправно сте нацртали шаблон за откључавање <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја од вас ће бити затражено да откључате ТВ помоћу података за пријављивање на Google.\n\n Покушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="2590227559763762751">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте нетачно унели шаблон за откључавање. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> несупешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу података за пријављивање на Google.\n\n Покушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
+ <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте неправилно нацртали шаблон за откључавање. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте погрешно унели лозинку. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте погрешно унели PIN. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте нетачно унели шаблон за откључавање. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> несупешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу података за пријављивање на Google.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"Неисправно сте нацртали шаблон за откључавање <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја од вас ће бити затражено да откључате ТВ помоћу података за пријављивање на Google.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="2590227559763762751">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте нетачно унели шаблон за откључавање. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> несупешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу података за пријављивање на Google.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="6128106399745755604">"Неправилно сте покушали да откључате таблет <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Након још неуспешних покушаја (<xliff:g id="NUMBER_1">%2$d</xliff:g>) таблет ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="950408382418270260">"Покушали сте да откључате ТВ нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја ТВ ће бити ресетован на подразумевана фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="8603565142156826565">"Неисправно сте покушали да откључате телефон <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Након још неуспешних покушаја (<xliff:g id="NUMBER_1">%2$d</xliff:g>) телефон ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="280873516493934365">"Неисправно сте покушали да откључате таблет <xliff:g id="NUMBER">%d</xliff:g> пута. Таблет ће сада бити враћен на подразумевана фабричка подешавања."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="3195755534096192191">"Покушали сте да откључате ТВ нетачно <xliff:g id="NUMBER">%d</xliff:g> пута. ТВ ће сада бити ресетован на подразумевана фабричка подешавања."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="3025504721764922246">"Неисправно сте покушали да откључате телефон <xliff:g id="NUMBER">%d</xliff:g> пута. Телефон ће сада бити враћен на подразумевана фабричка подешавања."</string>
- <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Покушајте поново за <xliff:g id="NUMBER">%d</xliff:g> секунде(и)."</string>
+ <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Пробајте поново за <xliff:g id="NUMBER">%d</xliff:g> секунде(и)."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Заборавили сте шаблон?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Откључавање налога"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"Превише покушаја уноса шаблона"</string>
@@ -1356,7 +1355,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB диск"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB меморија"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Измени"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Упозорење о потрошњи података"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Обавештење о потрошњи података"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Додирните за потрошњу и подешавања."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Нема више 2G-3G података"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Нема више 4G података"</string>
@@ -1422,7 +1421,7 @@
<string name="kg_wrong_pattern" msgid="1850806070801358830">"Погрешан шаблон"</string>
<string name="kg_wrong_password" msgid="2333281762128113157">"Погрешна лозинка"</string>
<string name="kg_wrong_pin" msgid="1131306510833563801">"Погрешан PIN"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Покушајте поново за <xliff:g id="NUMBER">%1$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Пробајте поново за <xliff:g id="NUMBER">%1$d</xliff:g> секунде(и)."</string>
<string name="kg_pattern_instructions" msgid="398978611683075868">"Нацртајте шаблон"</string>
<string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Унесите PIN SIM картице"</string>
<string name="kg_pin_instructions" msgid="2377242233495111557">"Унесите PIN"</string>
@@ -1444,18 +1443,18 @@
<string name="kg_login_invalid_input" msgid="5754664119319872197">"Неважеће корисничко име или лозинка."</string>
<string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"Заборавили сте корисничко име или лозинку?\nПосетите адресу "<b>"google.com/accounts/recovery"</b>"."</string>
<string name="kg_login_checking_password" msgid="1052685197710252395">"Провера налога…"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Унели сте нетачни PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Унели сте нетачну лозинку <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Унели сте нетачни PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Унели сте нетачну лозинку <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1575557200627128949">"Покушали сте да откључате таблет нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја таблет ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="5621231220154419413">"Покушали сте да откључате ТВ нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја ТВ ће бити ресетован на подразумевана фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="4051015943038199910">"Покушали сте да откључате телефон нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја телефон ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2072996269148483637">"Покушали сте да откључате таблет нетачно <xliff:g id="NUMBER">%d</xliff:g> пута. Таблет ће сада бити враћен на подразумевана фабричка подешавања."</string>
<string name="kg_failed_attempts_now_wiping" product="tv" msgid="4987878286750741463">"Покушали сте да откључате ТВ нетачно <xliff:g id="NUMBER">%d</xliff:g> пута. ТВ ће сада бити ресетован на подразумевана фабричка подешавања."</string>
<string name="kg_failed_attempts_now_wiping" product="default" msgid="4817627474419471518">"Покушали сте да откључате телефон нетачно <xliff:g id="NUMBER">%d</xliff:g> пута. Телефон ће сада бити враћен на подразумевана фабричка подешавања."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу налога е-поште.\n\nПокушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"Неисправно сте нацртали шаблон за откључавање <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате ТВ помоћу налога е-поште.\n\n Покушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу налога е-поште.\n\nПокушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"Неисправно сте нацртали шаблон за откључавање <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате ТВ помоћу налога е-поште.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
<string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Уклони"</string>
<string name="safe_media_volume_warning" product="default" msgid="2276318909314492312">"Желите да појачате звук изнад препорученог нивоа?\n\nСлушање гласне музике дуже време може да вам оштети слух."</string>
@@ -1566,14 +1565,14 @@
<string name="restr_pin_enter_new_pin" msgid="5959606691619959184">"Нови PIN"</string>
<string name="restr_pin_confirm_pin" msgid="8501523829633146239">"Потврдите нови PIN"</string>
<string name="restr_pin_create_pin" msgid="8017600000263450337">"Направите PIN за измену ограничења"</string>
- <string name="restr_pin_error_doesnt_match" msgid="2224214190906994548">"PIN-ови се не подударају. Покушајте поново."</string>
+ <string name="restr_pin_error_doesnt_match" msgid="2224214190906994548">"PIN-ови се не подударају. Пробајте поново."</string>
<string name="restr_pin_error_too_short" msgid="8173982756265777792">"PIN је прекратак. Мора да садржи најмање 4 цифре."</string>
<plurals name="restr_pin_countdown" formatted="false" msgid="9061246974881224688">
- <item quantity="one">Покушајте поново за <xliff:g id="COUNT">%d</xliff:g> секунду</item>
- <item quantity="few">Покушајте поново за <xliff:g id="COUNT">%d</xliff:g> секунде</item>
- <item quantity="other">Покушајте поново за <xliff:g id="COUNT">%d</xliff:g> секунди</item>
+ <item quantity="one">Пробајте поново за <xliff:g id="COUNT">%d</xliff:g> секунду</item>
+ <item quantity="few">Пробајте поново за <xliff:g id="COUNT">%d</xliff:g> секунде</item>
+ <item quantity="other">Пробајте поново за <xliff:g id="COUNT">%d</xliff:g> секунди</item>
</plurals>
- <string name="restr_pin_try_later" msgid="973144472490532377">"Покушајте поново касније"</string>
+ <string name="restr_pin_try_later" msgid="973144472490532377">"Пробајте поново касније"</string>
<string name="immersive_cling_title" msgid="8394201622932303336">"Приказује се цео екран"</string>
<string name="immersive_cling_description" msgid="3482371193207536040">"Да бисте изашли, превуците надоле одозго."</string>
<string name="immersive_cling_positive" msgid="5016839404568297683">"Важи"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a0b6db3..ac74164 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefonalternativ"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Skärmlås"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Stäng av"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Nödsituation"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Felrapport"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Skapa felrapport"</string>
<string name="bugreport_message" msgid="398447048750350456">"Nu hämtas information om aktuell status för enheten, som sedan skickas i ett e-postmeddelade. Det tar en liten stund innan felrapporten är färdig och kan skickas, så vi ber dig ha tålamod."</string>
@@ -668,7 +667,7 @@
<string name="keyguard_password_enter_password_code" msgid="1054721668279049780">"Ange lösenord för att låsa upp"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="6391755146112503443">"Ange PIN-kod för att låsa upp"</string>
<string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Fel PIN-kod."</string>
- <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 om du vill låsa upp."</string>
+ <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 för att låsa upp."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nödsamtalsnummer"</string>
<string name="lockscreen_carrier_default" msgid="6169005837238288522">"Ingen tjänst"</string>
<string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skärmen har låsts."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"USB-enhet (<xliff:g id="MANUFACTURER">%s</xliff:g>)"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB-lagring"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Redigera"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Varning angående dataanvändning"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Varning – dataanvändning"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Visa användning och inställning."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Datagränsen för 2G-3G har uppnåtts"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Datagränsen för 4G har uppnåtts"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2f5c4f9..4832546 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -212,8 +212,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Chaguo za simu"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Funga skrini"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Zima"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Dharura"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Ripoti ya hitilafu"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Chukua ripoti ya hitilafu"</string>
<string name="bugreport_message" msgid="398447048750350456">"Hii itakusanya maelezo kuhusu hali ya kifaa chako kwa sasa, na itume kama barua pepe. Itachukua muda mfupi tangu ripoti ya hitilafu ianze kuzalishwa hadi iwe tayari kutumwa; vumilia."</string>
@@ -1328,7 +1327,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Hifadhi ya USB iliyotengenezwa na <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Hifadhi ya USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Badilisha"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Onyo la matumizi ya data"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Tahadhari ya matumizi ya data"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Gonga ili uangalie matumizi na mipangilio."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Kikomo data ya 2G-3G kimefikiwa"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Kikomo cha data ya 4G kimefikiwa"</string>
diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml
index 1cb4062..f6bb25c 100644
--- a/core/res/res/values-ta-rIN/strings.xml
+++ b/core/res/res/values-ta-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB டிரைவ்"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB சேமிப்பிடம்"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"திருத்து"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"தரவு பயன்பாட்டு எச்சரிக்கை"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"தரவுப் பயன்பாடு குறித்த எச்சரிக்கை"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"தரவு உபயோகம், அமைப்புகளைப் பார்க்க, தட்டவும்."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G தரவு வரம்பைக் கடந்தது"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G தரவு வரம்பைக் கடந்தது"</string>
diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml
index e9aa6ba..24bfebd 100644
--- a/core/res/res/values-te-rIN/strings.xml
+++ b/core/res/res/values-te-rIN/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB డ్రైవ్"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB నిల్వ"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"సవరించు"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"డేటా వినియోగం హెచ్చరిక"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"డేటా వినియోగ హెచ్చరిక"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"వినియోగం,సెట్టింగ్ల కోసం నొక్కండి"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G డేటా పరిమితిని చేరుకుంది"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G డేటా పరిమితిని చేరుకుంది"</string>
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-th/strings.xml b/core/res/res/values-th/strings.xml
index f3445cb..818bb9f 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"ไดรฟ์ USB ของ <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"ที่เก็บข้อมูล USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"แก้ไข"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"คำเตือนการใช้ข้อมูล"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"การแจ้งเตือนการใช้อินเทอร์เน็ต"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"แตะเพื่อดูการใช้งานและการตั้งค่า"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"ถึงขีดจำกัดข้อมูล 2G-3G แล้ว"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"ถึงขีดจำกัดข้อมูล 4G แล้ว"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index ef1a57d..81c5fd0 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Pagpipilian sa telepono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Pag-lock sa screen"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"I-off"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Emergency"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Ulat sa bug"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Kunin ang ulat sa bug"</string>
<string name="bugreport_message" msgid="398447048750350456">"Mangongolekta ito ng impormasyon tungkol sa kasalukuyang katayuan ng iyong device, na ipapadala bilang mensaheng e-mail. Gugugol ito ng kaunting oras mula sa pagsisimula ng ulat sa bug hanggang sa handa na itong maipadala; mangyaring magpasensya."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB drive"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB storage"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"I-edit"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Babala sa paggamit ng data"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Alerto sa paggamit ng data"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"I-tap tingnan paggamit/setting."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Naabot na ang limitasyon sa 2G-3G data"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Naabot na ang limitasyon sa 4G data"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index daefd3d..9983142 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefon seçenekleri"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekran kilidi"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Kapat"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Acil durum"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Hata raporu"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Hata raporu al"</string>
<string name="bugreport_message" msgid="398447048750350456">"Bu rapor, e-posta iletisi olarak göndermek üzere cihazınızın şu anki durumuyla ilgili bilgi toplar. Hata raporu başlatıldıktan sonra hazır olması biraz zaman alabilir, lütfen sabırlı olun."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB sürücüsü"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB bellek"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Düzenle"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Veri kullanım uyarısı"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Veri kullanımı uyarısı"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Kul. ve ayar. gör. için dokunun."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G veri sınırına ulaşıldı"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G veri sınırına ulaşıldı"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 42ac191..fb0fda8 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -218,8 +218,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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1105,8 +1104,8 @@
<string name="volume_icon_description_incall" msgid="8890073218154543397">"Гучність сигналу виклику"</string>
<string name="volume_icon_description_media" msgid="4217311719665194215">"Гучність медіа"</string>
<string name="volume_icon_description_notification" msgid="7044986546477282274">"Гучність сповіщення"</string>
- <string name="ringtone_default" msgid="3789758980357696936">"Мелодія за умовч."</string>
- <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Мелодія за умовч. (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default" msgid="3789758980357696936">"Мелодія за умовчанням"</string>
+ <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Мелодія за умовчанням (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="7937634392408977062">"Немає"</string>
<string name="ringtone_picker_title" msgid="3515143939175119094">"Мелодії"</string>
<string name="ringtone_unknown" msgid="5477919988701784788">"Невідома мелодія"</string>
@@ -1382,7 +1381,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Носій USB (<xliff:g id="MANUFACTURER">%s</xliff:g>)"</string>
<string name="storage_usb" msgid="3017954059538517278">"Носій USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Редагувати"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Застереження про використ. даних"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Сповіщення про використ. трафіку"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Переглянути дані та параметри."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Досягнуто ліміту даних 2G–3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Досягнуто ліміту даних 4G"</string>
diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml
index e351ec0..d6eae64a 100644
--- a/core/res/res/values-ur-rPK/strings.xml
+++ b/core/res/res/values-ur-rPK/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB ڈرائیو"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB اسٹوریج"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"ترمیم کریں"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"ڈیٹا کے استعمال کی وارننگ"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"ڈیٹا کے استعمال کا الرٹ"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"استعمال اور ترتیبات دیکھنے کیلئے تھپتھپائیں۔"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G ڈیٹا کی حد کو پہنچ گیا"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G ڈیٹا کی حد کو پہنچ گیا"</string>
diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml
index d75291b..5b7cf74 100644
--- a/core/res/res/values-uz-rUZ/strings.xml
+++ b/core/res/res/values-uz-rUZ/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Telefon sozlamalari"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekran qulfi"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"O‘chirish"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Favqulodda chaqiruv"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Nosozlik haqida ma’lumot berish"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Xatoliklar hisoboti"</string>
<string name="bugreport_message" msgid="398447048750350456">"Qurilmangiz holati haqidagi ma’lumotlar to‘planib, e-pochta orqali yuboriladi. Hisobotni tayyorlash biroz vaqt olishi mumkin."</string>
@@ -1100,7 +1099,7 @@
<string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct yoniq"</string>
<string name="wifi_p2p_enabled_notification_message" msgid="8064677407830620023">"Sozlamalarni ochish uchun bosing"</string>
<string name="accept" msgid="1645267259272829559">"Qabul qilish"</string>
- <string name="decline" msgid="2112225451706137894">"Rad qilish"</string>
+ <string name="decline" msgid="2112225451706137894">"Rad etish"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"taklif jo‘natildi"</string>
<string name="wifi_p2p_invitation_to_connect_title" msgid="4958803948658533637">"ulanish taklifi"</string>
<string name="wifi_p2p_from_message" msgid="570389174731951769">"Kimdan:"</string>
@@ -1114,7 +1113,7 @@
<string name="sms_control_title" msgid="7296612781128917719">"SMS xabarlar yuborilmoqda"</string>
<string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> katta miqdordagi SMS xabarlarini jo‘natmoqda. Ushbu ilovaga xabarlar jo‘natishni davom ettirishga ruxsat berasizmi?"</string>
<string name="sms_control_yes" msgid="3663725993855816807">"Ruxsat berish"</string>
- <string name="sms_control_no" msgid="625438561395534982">"Rad qilish"</string>
+ <string name="sms_control_no" msgid="625438561395534982">"Rad etish"</string>
<string name="sms_short_code_confirm_message" msgid="1645436466285310855">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>ga xabar jo‘natishni xohlaydi."</string>
<string name="sms_short_code_details" msgid="5873295990846059400">"Bunda, mobil hisobingizdan "<b>"to‘lov olinishi mumkin"</b>"."</string>
<string name="sms_premium_short_code_details" msgid="7869234868023975"><b>"Bunda, mobil hisobingizdan to‘lov olinishi mumkin."</b></string>
@@ -1224,7 +1223,7 @@
<string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Ushbu so‘rovga ruxsat berishni xohlaysizmi?"</string>
<string name="grant_permissions_header_text" msgid="6874497408201826708">"Ruxsat so‘rovi"</string>
<string name="allow" msgid="7225948811296386551">"Ruxsat berish"</string>
- <string name="deny" msgid="2081879885755434506">"Rad qilish"</string>
+ <string name="deny" msgid="2081879885755434506">"Rad etish"</string>
<string name="permission_request_notification_title" msgid="6486759795926237907">"Ruxsat so‘raldi"</string>
<string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"<xliff:g id="ACCOUNT">%s</xliff:g> hisobi uchun\nruxsat so‘raldi"</string>
<string name="forward_intent_to_owner" msgid="1207197447013960896">"Siz ushbu ilovadan ishchi profilingizdan tashqarida foydalanmoqdasiz"</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB xotira qurilmasi"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB xotira"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Tahrirlash"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Trafik kam qoldi"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Trafik sarfi bo‘yicha ogohlantirish"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Trafik sarfi va sozlamalarni ko‘rish uchun bosing."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G trafik chekloviga yetdi"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G trafik chekloviga yetdi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 058999b..42e0b0a 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Tùy chọn điện thoại"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Khoá màn hình"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Tắt nguồn"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Khẩn cấp"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Báo cáo lỗi"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Nhận báo cáo lỗi"</string>
<string name="bugreport_message" msgid="398447048750350456">"Báo cáo này sẽ thu thập thông tin về tình trạng thiết bị hiện tại của bạn, để gửi dưới dạng thông báo qua email. Sẽ mất một chút thời gian kể từ khi bắt đầu báo cáo lỗi cho tới khi báo cáo sẵn sàng để gửi; xin vui lòng kiên nhẫn."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Ổ USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Bộ lưu trữ USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Chỉnh sửa"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Cảnh báo sử dụng dữ liệu"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Thông báo về sử dụng dữ liệu"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Nhấn để xem sử dụng và cài đặt."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Đã đạt tới giới hạn dữ liệu 2G-3G"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Đã đạt tới giới hạn dữ liệu 4G"</string>
diff --git a/core/res/res/values-watch/colors_device_defaults.xml b/core/res/res/values-watch/colors_device_defaults.xml
new file mode 100644
index 0000000..15786b4
--- /dev/null
+++ b/core/res/res/values-watch/colors_device_defaults.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.
+-->
+
+<!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
+ 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/colors_material.xml b/core/res/res/values-watch/colors_material.xml
index 54eece4..18bfd4d 100644
--- a/core/res/res/values-watch/colors_material.xml
+++ b/core/res/res/values-watch/colors_material.xml
@@ -17,8 +17,10 @@
<color name="background_material_dark">#ff232e33</color>
<color name="background_floating_material_dark">#ff3e5059</color>
- <color name="accent_material_dark">#ff5e97f6</color>
+ <color name="accent_material_700">#ff2e4978</color>
<color name="accent_material_light">#ff4285f4</color>
+ <color name="accent_material_dark">#ff5e97f6</color>
+ <color name="accent_material_50">#ffd0def7</color>
<color name="primary_material_dark">#4D4D4D</color>
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-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index 8a080d9c..af4207e 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -95,7 +95,7 @@
</style>
<style name="DialogWindowTitle.Material">
- <item name="maxLines">3</item>
+ <item name="maxLines">@empty</item>
<item name="scrollHorizontally">false</item>
<item name="textAppearance">@style/TextAppearance.Material.DialogWindowTitle</item>
<item name="gravity">@integer/config_dialogTextGravity</item>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 2313b26..aa1594d 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2011 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.
@@ -18,26 +18,328 @@
===============================================================
PLEASE READ
===============================================================
-This file contains the themes that are the Device Defaults.
-If you want to edit themes to skin your device, do it here.
-We recommend that you do not edit themes.xml and instead edit
-this file.
-
-Editing this file instead of themes.xml will greatly simplify
+This file contains the themes that are the Device Defaults on
+Watch. If you want to edit themes to skin your device, do it
+here. Editing this file instead of themes.xml will greatly simplify
merges for future platform versions and CTS compliance will be
easier.
+
+You should also have a look at themes.xml, to
+understand why we define Dialogs and Settings with color
+palette Dark. It's because themes.xml modify Material in
+a similar way.
===============================================================
PLEASE READ
===============================================================
-->
<resources>
+ <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault} with no action bar -->
+ <style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Material.NoActionBar">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar. This theme
+ sets {@link android.R.attr#windowFullscreen} to true. -->
+ <style name="Theme.DeviceDefault.NoActionBar.Fullscreen" parent="Theme.Material.NoActionBar.Fullscreen">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and
+ extending in to overscan region. This theme
+ sets {@link android.R.attr#windowFullscreen} and {@link android.R.attr#windowOverscan}
+ to true. -->
+ <style name="Theme.DeviceDefault.NoActionBar.Overscan" parent="Theme.Material.NoActionBar.Overscan">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent
+ system decor. This theme sets {@link android.R.attr#windowTranslucentStatus} and
+ {@link android.R.attr#windowTranslucentNavigation} to true. -->
+ <style name="Theme.DeviceDefault.NoActionBar.TranslucentDecor" parent="Theme.Material.NoActionBar.TranslucentDecor">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
<!-- Theme used for the intent picker activity. -->
- <style name="Theme.DeviceDefault.Resolver" parent="Theme.Material">
+ <style name="Theme.DeviceDefault.Resolver">
<item name="colorControlActivated">?attr/colorControlHighlight</item>
<item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
<item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
</style>
<!-- Use a dark theme for watches. -->
- <style name="Theme.DeviceDefault.System" parent="Theme.Material" />
+ <style name="Theme.DeviceDefault.System" />
+
+ <!-- DeviceDefault style for input methods, which is used by the
+ {@link android.inputmethodservice.InputMethodService} class.-->
+ <style name="Theme.DeviceDefault.InputMethod" parent="Theme.DeviceDefault.Panel">
+ <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
+ <item name="imeFullscreenBackground">?colorBackground</item>
+ <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
+ </style>
+
+ <!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
+ watch theme is not floating. You can set this theme on an activity if you would like to make
+ an activity that looks like a Dialog.-->
+ <style name="Theme.DeviceDefault.Dialog" parent="Theme.Material.Dialog" >
+ <item name="windowIsFloating">false</item>
+ <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
+ <item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
+
+ <item name="buttonBarStyle">@style/DeviceDefault.ButtonBar.AlertDialog</item>
+ <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless.Small</item>
+
+ <item name="textAppearance">@style/TextAppearance.DeviceDefault</item>
+ <item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item>
+
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for a window that should look like the Settings app. -->
+ <style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault"/>
+ <style name="Theme.DeviceDefault.Settings.NoActionBar" parent="Theme.DeviceDefault"/>
+ <style name="Theme.DeviceDefault.Settings.BaseDialog" parent="Theme.DeviceDefault.Dialog"/>
+ <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.DeviceDefault.Settings.BaseDialog"/>
+ <style name="Theme.DeviceDefault.Settings.DialogWhenLarge" parent="Theme.DeviceDefault.DialogWhenLarge"/>
+ <style name="Theme.DeviceDefault.Settings.DialogWhenLarge.NoActionBar" parent="Theme.DeviceDefault.DialogWhenLarge.NoActionBar"/>
+ <style name="Theme.DeviceDefault.Settings.Dialog.Presentation" parent="Theme.DeviceDefault.Dialog.Presentation"/>
+ <style name="Theme.DeviceDefault.Settings.SearchBar" parent="Theme.DeviceDefault.SearchBar"/>
+
+ <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
+ <item name="windowIsFloating">false</item>
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.Settings.CompactMenu" parent="Theme.Material.CompactMenu">
+ <item name="windowIsFloating">false</item>
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a
+ regular dialog. -->
+ <style name="Theme.DeviceDefault.Dialog.MinWidth" parent="Theme.Material.Dialog.MinWidth">
+ <item name="windowIsFloating">false</item>
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
+ <style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="Theme.Material.Dialog.NoActionBar">
+ <item name="windowIsFloating">false</item>
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
+ for a regular dialog. -->
+ <style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Material.Dialog.NoActionBar.MinWidth">
+ <item name="windowIsFloating">false</item>
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for a window that will be displayed either full-screen on smaller
+ screens (small, normal) or as a dialog on larger screens (large, xlarge). -->
+ <style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Material.DialogWhenLarge">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for a window without an action bar that will be displayed either
+ full-screen on smaller screens (small, normal) or as a dialog on larger screens (large,
+ xlarge). -->
+ <style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" parent="Theme.Material.DialogWhenLarge.NoActionBar">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for a presentation window on a secondary display. -->
+ <style name="Theme.DeviceDefault.Dialog.Presentation" parent="Theme.Material.Dialog.Presentation">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for panel windows. This removes all extraneous window
+ decorations, so you basically have an empty rectangle in which to place your content. It makes
+ the window floating, with a transparent background, and turns off dimming behind the window. -->
+ <style name="Theme.DeviceDefault.Panel" parent="Theme.Material.Panel">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
+ behind them. -->
+ <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Material.Wallpaper">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
+ behind them and without an action bar. -->
+ <style name="Theme.DeviceDefault.Wallpaper.NoTitleBar" parent="Theme.Material.Wallpaper.NoTitleBar">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <!-- DeviceDefault style for input methods, which is used by the
+ {@link android.service.voice.VoiceInteractionSession} class.-->
+ <style name="Theme.DeviceDefault.VoiceInteractionSession" parent="Theme.Material.VoiceInteractionSession">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
+ <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
+
+ <!-- Color palette Dialog -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar">
+ <!-- Color palette Dark -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">@color/background_device_default_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
+ <item name="windowIsFloating">false</item>
+ <!-- Color palette Dialog -->
+ <item name="colorPrimary">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+ <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+ <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+ </style>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 5d7ddcb..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>
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> U 盘"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB存储器"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"修改"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"流量警告"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"流量消耗提醒"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"点按即可查看使用情况和设置。"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"已达到2G-3G流量上限"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"已达到4G流量上限"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 25a0276..62bb44a 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB 驅動器"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB 儲存裝置"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"編輯"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"資料用量警告"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"數據用量警告"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"輕按即可查看用量和設定。"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"已達到 2G-3G 數據流量上限"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"已達到 4G 數據流量上限"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 3b1296c..0500c07 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -214,8 +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>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <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>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB 隨身碟"</string>
<string name="storage_usb" msgid="3017954059538517278">"USB 儲存裝置"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"編輯"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"數據用量警告"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"數據用量警告"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"輕觸即可查看用量和設定。"</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"已達到 2G-3G 數據流量上限"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"已達到 4G 數據流量上限"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index b50e76c..849f292 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -214,8 +214,7 @@
<string name="global_actions" product="default" msgid="2406416831541615258">"Okukhethwa kukho kwefoni"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ukuvala isikrini"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Vala amandla"</string>
- <!-- no translation found for global_action_emergency (7112311161137421166) -->
- <skip />
+ <string name="global_action_emergency" msgid="7112311161137421166">"Isimo esiphuthumayo"</string>
<string name="global_action_bug_report" msgid="7934010578922304799">"Umbiko wephutha"</string>
<string name="bugreport_title" msgid="2667494803742548533">"Thatha umbiko wesiphazamiso"</string>
<string name="bugreport_message" msgid="398447048750350456">"Lokhu kuzoqoqa ulwazi mayelana nesimo samanje sedivayisi yakho, ukuthumela imilayezo ye-imeyili. Kuzothatha isikhathi esincane kusuka ekuqaleni umbiko wesiphazamiso uze ulungele ukuthunyelwa; sicela ubekezele."</string>
@@ -1330,7 +1329,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"<xliff:g id="MANUFACTURER">%s</xliff:g> idrayivu ye-USB"</string>
<string name="storage_usb" msgid="3017954059538517278">"Isitoreji se-USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Hlela"</string>
- <string name="data_usage_warning_title" msgid="1955638862122232342">"Isexwayiso sokusetshenziswa kwedatha"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Izexwayiso zokusetshenziswa kwedatha"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Thepha ukuze ubuke ukusetshenziswa nezilungiselelo."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"2G-3G umkhawulo wedatha ufinyelelwe"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"4G umkhawulo wedatha ufinyelelwe"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f0a25aa..c3967e4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3965,6 +3965,7 @@
</declare-styleable>
<declare-styleable name="ProgressBar">
+ <attr name="min" format="integer" />
<!-- Defines the maximum value the progress can take. -->
<attr name="max" format="integer" />
<!-- Defines the default progress value, between 0 and max. -->
@@ -7941,6 +7942,14 @@
<declare-styleable name="SeekBarPreference">
<attr name="layout" />
+ <!-- Attribute indicating whether the slider within this preference can be adjusted, that is
+ pressing left/right keys when this preference is focused will move the slider accordingly (e.g.
+ inline adjustable preferences). False, if the slider within the preference is read-only and
+ cannot be adjusted. By default, the seekbar is adjustable. -->
+ <attr name="adjustable" format="boolean" />
+ <!-- Flag indicating whether the TextView next to the seekbar that shows the current seekbar value will be
+ displayed. If true, the view is VISIBLE; if false, the view will be GONE. By default, this view is VISIBLE. -->
+ <attr name="showSeekBarValue" format="boolean" />
</declare-styleable>
<!-- Base attributes available to PreferenceFragment. -->
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 27ee27b..89691e9 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -32,4 +32,9 @@
<color name="accent_device_default_light">@color/accent_material_light</color>
<color name="accent_device_default_dark">@color/accent_material_dark</color>
<color name="accent_device_default_50">@color/material_deep_teal_50</color>
+
+ <color name="background_device_default_dark">@color/background_material_dark</color>
+ <color name="background_device_default_light">@color/background_material_light</color>
+ <color name="background_floating_device_default_dark">@color/background_floating_material_dark</color>
+ <color name="background_floating_device_default_light">@color/background_floating_material_light</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3ae6186..5df33bd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1715,6 +1715,12 @@
turned off and the screen off animation has been performed. -->
<bool name="config_dozeAfterScreenOff">false</bool>
+ <!-- Doze: should the TYPE_PICK_UP_GESTURE sensor be used as a pulse signal. -->
+ <bool name="config_dozePulsePickup">false</bool>
+
+ <!-- Type of the double tap sensor. Empty if double tap is not supported. -->
+ <string name="config_dozeDoubleTapSensorType" translatable="false"></string>
+
<!-- Power Management: Specifies whether to decouple the auto-suspend state of the
device from the display on/off state.
@@ -1934,19 +1940,6 @@
where if the preferred is used we don't try the others. -->
<bool name="config_dontPreferApn">false</bool>
- <!-- The list of ril radio technologies (see ServiceState.java) which only support
- a single data connection at one time. This may change by carrier via
- overlays (some don't support multiple pdp on UMTS). All unlisted radio
- tech types support unlimited types (practically only 2-4 used). -->
- <integer-array name="config_onlySingleDcAllowed">
- <item>4</item> <!-- IS95A -->
- <item>5</item> <!-- IS95B -->
- <item>6</item> <!-- 1xRTT -->
- <item>7</item> <!-- EVDO_0 -->
- <item>8</item> <!-- EVDO_A -->
- <item>12</item> <!-- EVDO_B -->
- </integer-array>
-
<!-- Set to true if after a provisioning apn the radio should be restarted -->
<bool name="config_restartRadioAfterProvisioning">false</bool>
@@ -2212,9 +2205,6 @@
provisioning, availability etc -->
<bool name="config_carrier_volte_available">false</bool>
- <!-- Flag specifying whether VoLTE availability is based on provisioning -->
- <bool name="config_carrier_volte_provisioned">false</bool>
-
<!-- Flag specifying whether VoLTE TTY is supported -->
<bool name="config_carrier_volte_tty_supported">true</bool>
@@ -2234,6 +2224,11 @@
<!-- Flag specifying whether WFC over IMS is available on device -->
<bool name="config_device_wfc_ims_available">false</bool>
+ <!-- Flag specifying whether WFC over IMS should be available for carrier: independent of
+ carrier provisioning. If false: hard disabled. If true: then depends on carrier
+ provisioning, availability etc -->
+ <bool name="config_carrier_wfc_ims_available">false</bool>
+
<bool name="config_networkSamplingWakesDevice">true</bool>
<string-array translatable="false" name="config_cdma_home_system" />
@@ -2338,22 +2333,6 @@
default scale-up transition. -->
<bool name="config_overrideRemoteViewsActivityTransition">false</bool>
- <!-- This config is used to check if the carrier requires converting destination
- number before sending out a SMS.
- Formats for this configuration as below:
- [true or false][;optional gid]
- The logic to pick up the configuration:
- (1) If the "config_sms_convert_destination_number_support" array has no gid
- special item, the last one will be picked
- (2) If the "config_sms_convert_destination_number_support" array has gid special
- item and it matches the current sim's gid, it will be picked.
- (3) If the "config_sms_convert_destination_number_support" array has gid special
- item but it doesn't match the current sim's gid, the last one without gid
- will be picked -->
- <string-array translatable="false" name="config_sms_convert_destination_number_support">
- <item>false</item>
- </string-array>
-
<!-- The maximum bitmap size that can be written to a MediaMetadata object. This value
is the max width/height allowed in dips.-->
<dimen name="config_mediaMetadataBitmapMaxSize">320dp</dimen>
@@ -2493,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/public.xml b/core/res/res/values/public.xml
index 7bce379..7999e7e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2764,4 +2764,5 @@
<public-group type="id" first-id="0x01020041">
</public-group>
+ <public type="attr" name="min" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bf7317c..cce02f2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3561,7 +3561,7 @@
<!-- Button text for the edit menu in input method extract mode. [CHAR LIMIT=16] -->
<string name="extract_edit_menu_button">Edit</string>
- <!-- Notification title when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
+ <!-- Notification title when data usage has exceeded warning threshold. [CHAR LIMIT=50] -->
<string name="data_usage_warning_title">Data usage alert</string>
<!-- Notification body when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
<string name="data_usage_warning_body">Tap to view usage and settings.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 449acc6..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" />
@@ -347,8 +349,6 @@
<java-symbol type="integer" name="config_wifi_operating_voltage_mv" />
<java-symbol type="string" name="config_wifi_framework_sap_2G_channel_list" />
- <java-symbol type="bool" name="editable_voicemailnumber" />
-
<java-symbol type="bool" name="config_wifi_framework_cellular_handover_enable_user_triggered_adjustment" />
<java-symbol type="integer" name="config_wifi_framework_associated_full_scan_tx_packet_threshold" />
<java-symbol type="integer" name="config_wifi_framework_associated_full_scan_rx_packet_threshold" />
@@ -1667,7 +1667,6 @@
<java-symbol type="array" name="config_testLocationProviders" />
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_notificationFallbackVibePattern" />
- <java-symbol type="array" name="config_onlySingleDcAllowed" />
<java-symbol type="bool" name="config_useAttentionLight" />
<java-symbol type="bool" name="config_animateScreenLights" />
<java-symbol type="bool" name="config_automatic_brightness_available" />
@@ -2221,12 +2220,12 @@
<java-symbol type="bool" name="imsServiceAllowTurnOff" />
<java-symbol type="bool" name="config_device_volte_available" />
<java-symbol type="bool" name="config_carrier_volte_available" />
- <java-symbol type="bool" name="config_carrier_volte_provisioned" />
<java-symbol type="bool" name="config_carrier_volte_tty_supported" />
<java-symbol type="bool" name="config_device_vt_available" />
<java-symbol type="bool" name="config_device_respects_hold_carrier_config" />
<java-symbol type="bool" name="config_carrier_vt_available" />
<java-symbol type="bool" name="config_device_wfc_ims_available" />
+ <java-symbol type="bool" name="config_carrier_wfc_ims_available" />
<java-symbol type="attr" name="touchscreenBlocksFocus" />
<java-symbol type="layout" name="resolver_list_with_default" />
<java-symbol type="string" name="whichApplicationNamed" />
@@ -2282,7 +2281,6 @@
<java-symbol type="bool" name="config_overrideRemoteViewsActivityTransition" />
<java-symbol type="layout" name="simple_account_item" />
- <java-symbol type="array" name="config_sms_convert_destination_number_support" />
<java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" />
<java-symbol type="id" name="profile_button" />
@@ -2681,6 +2679,9 @@
<java-symbol type="string" name="config_emergency_call_number" />
<java-symbol type="array" name="config_emergency_mcc_codes" />
+ <java-symbol type="string" name="config_dozeDoubleTapSensorType" />
+ <java-symbol type="bool" name="config_dozePulsePickup" />
+
<!-- Used for MimeIconUtils. -->
<java-symbol type="drawable" name="ic_doc_apk" />
<java-symbol type="drawable" name="ic_doc_audio" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 0e98ade..b19858e 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -49,7 +49,7 @@
Type.DeviceDefault.Etc (for example, {@code Widget.DeviceDefault.Button} and
{@code TextAppearance.DeviceDefault.Widget.PopupMenu.Large}).</p>
-->
- <style name="Theme.DeviceDefault" parent="Theme.Material" >
+ <style name="Theme.DeviceDefaultBase" parent="Theme.Material" >
<!-- Text styles -->
<item name="textAppearance">@style/TextAppearance.DeviceDefault</item>
<item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item>
@@ -206,6 +206,8 @@
</style>
+ <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
+
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar -->
<style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Material.NoActionBar">
<!-- Color palette -->
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/res/res/xml/preferred_time_zones.xml b/core/res/res/xml/preferred_time_zones.xml
deleted file mode 100644
index da8553f..0000000
--- a/core/res/res/xml/preferred_time_zones.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/default/default/data/preferred_time_zones.xml
-**
-** Copyright 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.
-*/
--->
-<timezones>
- <timezone offset="-18000000">America/New_York</timezone>
- <timezone offset="-21600000">America/Chicago</timezone>
- <timezone offset="-25200000">America/Denver</timezone>
- <timezone offset="-28800000">America/Los_Angeles</timezone>
-</timezones>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index a9fa5e0d..29c6b79 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -57,6 +57,12 @@
<!-- Bulgaria: 4-5 digits, plus EU -->
<shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}" />
+ <!-- Bahrain: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="bh" pattern="\\d{1,5}" free="81181" />
+
+ <!-- Brazil: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d" />
+
<!-- Belarus: 4 digits -->
<shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
@@ -129,7 +135,7 @@
<!-- Italy: 5 digits (premium=4xxxx), plus EU:
http://clients.txtnation.com/attachments/token/di5kfblvubttvlw/?name=Italy_CASP_EN.pdf -->
- <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}|4112503" />
+ <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}|4112503" standard="43\\d{3}" />
<!-- Japan: 8083 used by SOFTBANK_DCB_2 -->
<shortcode country="jp" free="8083" />
@@ -204,7 +210,7 @@
<shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}" />
<!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
- <shortcode country="sk" premium="\\d{4}" free="116\\d{3}" />
+ <shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" />
<!-- Thailand: 4186001 used by AIS_TH_DCB -->
<shortcode country="th" free="4186001" />
@@ -220,9 +226,9 @@
<!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm),
visual voicemail code for T-Mobile: 122 -->
- <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567" free="122|87902" />
+ <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245" />
<!-- Vietnam: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="vn" pattern="\\d{1,5}" free="5001" />
+ <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" />
</shortcodes>
diff --git a/core/res/res/xml/time_zones_by_country.xml b/core/res/res/xml/time_zones_by_country.xml
index 9b2bd50..a685e2b 100644
--- a/core/res/res/xml/time_zones_by_country.xml
+++ b/core/res/res/xml/time_zones_by_country.xml
@@ -31,11 +31,11 @@
<!-- ANTIGUA AND BARBUDA, -4:00 -->
- <timezone code="ag">America/Antigua</timezone>
+ <timezone code="ag">America/Port_of_Spain</timezone>
<!-- ANGUILLA, -4:00 -->
- <timezone code="ai">America/Anguilla</timezone>
+ <timezone code="ai">America/Port_of_Spain</timezone>
<!-- ALBANIA, 1:00 -->
@@ -45,18 +45,13 @@
<timezone code="am">Asia/Yerevan</timezone>
- <!-- NETHERLANDS ANTILLES, -4:00 -->
-
- <timezone code="an">America/Curacao</timezone>
-
<!-- ANGOLA, 1:00 -->
- <timezone code="ao">Africa/Luanda</timezone>
+ <timezone code="ao">Africa/Lagos</timezone>
<!-- ANTARCTICA, 12:00 -->
- <timezone code="aq">Antarctica/McMurdo</timezone>
- <timezone code="aq">Antarctica/South_Pole</timezone>
+ <timezone code="aq">Pacific/Auckland</timezone>
<!-- ANTARCTICA, 10:00 -->
@@ -70,15 +65,22 @@
<timezone code="aq">Antarctica/Davis</timezone>
- <!-- ANTARCTICA, 6:00 -->
+ <!-- ANTARCTICA, 5:00 -->
<timezone code="aq">Antarctica/Mawson</timezone>
+
+ <!-- ANTARCTICA, 6:00 -->
+
<timezone code="aq">Antarctica/Vostok</timezone>
<!-- ANTARCTICA, 3:00 -->
<timezone code="aq">Antarctica/Syowa</timezone>
+ <!-- ANTARCTICA, 0:00 -->
+
+ <timezone code="aq">Antarctica/Troll</timezone>
+
<!-- ANTARCTICA, -3:00 -->
<timezone code="aq">Antarctica/Rothera</timezone>
@@ -91,12 +93,14 @@
<timezone code="ar">America/Argentina/Buenos_Aires</timezone>
<timezone code="ar">America/Argentina/Cordoba</timezone>
+ <timezone code="ar">America/Argentina/Salta</timezone>
<timezone code="ar">America/Argentina/Jujuy</timezone>
<timezone code="ar">America/Argentina/Tucuman</timezone>
<timezone code="ar">America/Argentina/Catamarca</timezone>
<timezone code="ar">America/Argentina/La_Rioja</timezone>
<timezone code="ar">America/Argentina/San_Juan</timezone>
<timezone code="ar">America/Argentina/Mendoza</timezone>
+ <timezone code="ar">America/Argentina/San_Luis</timezone>
<timezone code="ar">America/Argentina/Rio_Gallegos</timezone>
<timezone code="ar">America/Argentina/Ushuaia</timezone>
@@ -117,6 +121,9 @@
<timezone code="au">Australia/Currie</timezone>
<timezone code="au">Australia/Lindeman</timezone>
+ <!-- AUSTRALIA, 11:00 -->
+ <timezone code="au">Antarctica/Macquarie</timezone>
+
<!-- AUSTRALIA, 10:30 -->
<timezone code="au">Australia/Lord_Howe</timezone>
@@ -137,11 +144,11 @@
<!-- ARUBA, -4:00 -->
- <timezone code="aw">America/Aruba</timezone>
+ <timezone code="aw">America/Curacao</timezone>
<!-- ALAND ISLANDS, 2:00 -->
- <timezone code="ax">Europe/Mariehamn</timezone>
+ <timezone code="ax">Europe/Helsinki</timezone>
<!-- AZERBAIJAN, 4:00 -->
@@ -149,7 +156,7 @@
<!-- BOSNIA AND HERZEGOVINA, 1:00 -->
- <timezone code="ba">Europe/Sarajevo</timezone>
+ <timezone code="ba">Europe/Belgrade</timezone>
<!-- BARBADOS, -4:00 -->
@@ -165,7 +172,7 @@
<!-- BURKINA FASO, 0:00 -->
- <timezone code="bf">Africa/Ouagadougou</timezone>
+ <timezone code="bf">Africa/Abidjan</timezone>
<!-- BULGARIA, 2:00 -->
@@ -173,15 +180,19 @@
<!-- BAHRAIN, 3:00 -->
- <timezone code="bh">Asia/Bahrain</timezone>
+ <timezone code="bh">Asia/Qatar</timezone>
<!-- BURUNDI, 2:00 -->
- <timezone code="bi">Africa/Bujumbura</timezone>
+ <timezone code="bi">Africa/Maputo</timezone>
<!-- BENIN, 1:00 -->
- <timezone code="bj">Africa/Porto-Novo</timezone>
+ <timezone code="bj">Africa/Lagos</timezone>
+
+ <!-- Saint Barthélemy, -4:00 -->
+
+ <timezone code="bl">America/Port_of_Spain</timezone>
<!-- BERMUDA, -4:00 -->
@@ -195,6 +206,10 @@
<timezone code="bo">America/La_Paz</timezone>
+ <!-- Caribbean Netherlands, -4:00 -->
+
+ <timezone code="bq">America/Curacao</timezone>
+
<!-- BRAZIL, -2:00 -->
<timezone code="br">America/Noronha</timezone>
@@ -208,6 +223,7 @@
<timezone code="br">America/Araguaina</timezone>
<timezone code="br">America/Maceio</timezone>
<timezone code="br">America/Bahia</timezone>
+ <timezone code="br">America/Santarem</timezone>
<!-- BRAZIL, -4:00 -->
@@ -216,6 +232,9 @@
<timezone code="br">America/Cuiaba</timezone>
<timezone code="br">America/Porto_Velho</timezone>
<timezone code="br">America/Boa_Vista</timezone>
+
+ <!-- BRAZIL, -5:00 -->
+
<timezone code="br">America/Eirunepe</timezone>
<timezone code="br">America/Rio_Branco</timezone>
@@ -229,9 +248,9 @@
<!-- BOTSWANA, 2:00 -->
- <timezone code="bw">Africa/Gaborone</timezone>
+ <timezone code="bw">Africa/Maputo</timezone>
- <!-- BELARUS, 2:00 -->
+ <!-- BELARUS, 3:00 -->
<timezone code="by">Europe/Minsk</timezone>
@@ -254,12 +273,10 @@
<!-- CANADA, -5:00 -->
<timezone code="ca">America/Toronto</timezone>
- <timezone code="ca">America/Montreal</timezone>
<timezone code="ca">America/Nipigon</timezone>
<timezone code="ca">America/Thunder_Bay</timezone>
<timezone code="ca">America/Iqaluit</timezone>
<timezone code="ca">America/Pangnirtung</timezone>
- <timezone code="ca">America/Resolute</timezone>
<timezone code="ca">America/Atikokan</timezone>
<!-- CANADA, -6:00 -->
@@ -269,6 +286,7 @@
<timezone code="ca">America/Rankin_Inlet</timezone>
<timezone code="ca">America/Rainy_River</timezone>
<timezone code="ca">America/Swift_Current</timezone>
+ <timezone code="ca">America/Resolute</timezone>
<!-- CANADA, -7:00 -->
@@ -277,6 +295,8 @@
<timezone code="ca">America/Yellowknife</timezone>
<timezone code="ca">America/Inuvik</timezone>
<timezone code="ca">America/Dawson_Creek</timezone>
+ <timezone code="ca">America/Creston</timezone>
+ <timezone code="ca">America/Fort_Nelson</timezone>
<!-- CANADA, -8:00 -->
@@ -290,19 +310,19 @@
<!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 2:00 -->
- <timezone code="cd">Africa/Lubumbashi</timezone>
+ <timezone code="cd">Africa/Maputo</timezone>
<!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 1:00 -->
- <timezone code="cd">Africa/Kinshasa</timezone>
+ <timezone code="cd">Africa/Lagos</timezone>
<!-- CENTRAL AFRICAN REPUBLIC, 1:00 -->
- <timezone code="cf">Africa/Bangui</timezone>
+ <timezone code="cf">Africa/Lagos</timezone>
<!-- CONGO, 1:00 -->
- <timezone code="cg">Africa/Brazzaville</timezone>
+ <timezone code="cg">Africa/Lagos</timezone>
<!-- SWITZERLAND, 1:00 -->
@@ -326,15 +346,15 @@
<!-- CAMEROON, 1:00 -->
- <timezone code="cm">Africa/Douala</timezone>
+ <timezone code="cm">Africa/Lagos</timezone>
<!-- CHINA, 8:00 -->
<timezone code="cn">Asia/Shanghai</timezone>
- <timezone code="cn">Asia/Harbin</timezone>
- <timezone code="cn">Asia/Chongqing</timezone>
+
+ <!-- CHINA, 6:00 -->
+
<timezone code="cn">Asia/Urumqi</timezone>
- <timezone code="cn">Asia/Kashgar</timezone>
<!-- COLOMBIA, -5:00 -->
@@ -352,6 +372,10 @@
<timezone code="cv">Atlantic/Cape_Verde</timezone>
+ <!-- Curaçao, -4:00 -->
+
+ <timezone code="cw">America/Curacao</timezone>
+
<!-- CHRISTMAS ISLAND, 7:00 -->
<timezone code="cx">Indian/Christmas</timezone>
@@ -367,10 +391,11 @@
<!-- GERMANY, 1:00 -->
<timezone code="de">Europe/Berlin</timezone>
+ <timezone code="de">Europe/Zurich</timezone>
<!-- DJIBOUTI, 3:00 -->
- <timezone code="dj">Africa/Djibouti</timezone>
+ <timezone code="dj">Africa/Nairobi</timezone>
<!-- DENMARK, 1:00 -->
@@ -378,7 +403,7 @@
<!-- DOMINICA, -4:00 -->
- <timezone code="dm">America/Dominica</timezone>
+ <timezone code="dm">America/Port_of_Spain</timezone>
<!-- DOMINICAN REPUBLIC, -4:00 -->
@@ -410,7 +435,7 @@
<!-- ERITREA, 3:00 -->
- <timezone code="er">Africa/Asmara</timezone>
+ <timezone code="er">Africa/Nairobi</timezone>
<!-- SPAIN, 1:00 -->
@@ -423,7 +448,7 @@
<!-- ETHIOPIA, 3:00 -->
- <timezone code="et">Africa/Addis_Ababa</timezone>
+ <timezone code="et">Africa/Nairobi</timezone>
<!-- FINLAND, 2:00 -->
@@ -433,7 +458,7 @@
<timezone code="fj">Pacific/Fiji</timezone>
- <!-- FALKLAND ISLANDS (MALVINAS), -4:00 -->
+ <!-- FALKLAND ISLANDS (MALVINAS), -3:00 -->
<timezone code="fk">Atlantic/Stanley</timezone>
@@ -444,7 +469,7 @@
<!-- MICRONESIA, FEDERATED STATES OF, 10:00 -->
- <timezone code="fm">Pacific/Truk</timezone>
+ <timezone code="fm">Pacific/Chuuk</timezone>
<!-- FAROE ISLANDS, 0:00 -->
@@ -456,7 +481,7 @@
<!-- GABON, 1:00 -->
- <timezone code="ga">Africa/Libreville</timezone>
+ <timezone code="ga">Africa/Lagos</timezone>
<!-- UNITED KINGDOM, 0:00 -->
@@ -464,7 +489,7 @@
<!-- GRENADA, -4:00 -->
- <timezone code="gd">America/Grenada</timezone>
+ <timezone code="gd">America/Port_of_Spain</timezone>
<!-- GEORGIA, 4:00 -->
@@ -476,7 +501,7 @@
<!-- GUERNSEY, 0:00 -->
- <timezone code="gg">Europe/Guernsey</timezone>
+ <timezone code="gg">Europe/London</timezone>
<!-- GHANA, 0:00 -->
@@ -504,19 +529,19 @@
<!-- GAMBIA, 0:00 -->
- <timezone code="gm">Africa/Banjul</timezone>
+ <timezone code="gm">Africa/Abidjan</timezone>
<!-- GUINEA, 0:00 -->
- <timezone code="gn">Africa/Conakry</timezone>
+ <timezone code="gn">Africa/Abidjan</timezone>
<!-- GUADELOUPE, -4:00 -->
- <timezone code="gp">America/Guadeloupe</timezone>
+ <timezone code="gp">America/Port_of_Spain</timezone>
<!-- EQUATORIAL GUINEA, 1:00 -->
- <timezone code="gq">Africa/Malabo</timezone>
+ <timezone code="gq">Africa/Lagos</timezone>
<!-- GREECE, 2:00 -->
@@ -552,7 +577,7 @@
<!-- CROATIA, 1:00 -->
- <timezone code="hr">Europe/Zagreb</timezone>
+ <timezone code="hr">Europe/Belgrade</timezone>
<!-- HAITI, -5:00 -->
@@ -585,11 +610,11 @@
<!-- ISLE OF MAN, 0:00 -->
- <timezone code="im">Europe/Isle_of_Man</timezone>
+ <timezone code="im">Europe/London</timezone>
<!-- INDIA, 5:30 -->
- <timezone code="in">Asia/Calcutta</timezone>
+ <timezone code="in">Asia/Kolkata</timezone>
<!-- BRITISH INDIAN OCEAN TERRITORY, 6:00 -->
@@ -613,7 +638,7 @@
<!-- JERSEY, 0:00 -->
- <timezone code="je">Europe/Jersey</timezone>
+ <timezone code="je">Europe/London</timezone>
<!-- JAMAICA, -5:00 -->
@@ -637,7 +662,7 @@
<!-- CAMBODIA, 7:00 -->
- <timezone code="kh">Asia/Phnom_Penh</timezone>
+ <timezone code="kh">Asia/Bangkok</timezone>
<!-- KIRIBATI, 14:00 -->
@@ -653,13 +678,13 @@
<!-- COMOROS, 3:00 -->
- <timezone code="km">Indian/Comoro</timezone>
+ <timezone code="km">Africa/Nairobi</timezone>
<!-- SAINT KITTS AND NEVIS, -4:00 -->
- <timezone code="kn">America/St_Kitts</timezone>
+ <timezone code="kn">America/Port_of_Spain</timezone>
- <!-- KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF, 9:00 -->
+ <!-- KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF, 8:30 -->
<timezone code="kp">Asia/Pyongyang</timezone>
@@ -669,11 +694,11 @@
<!-- KUWAIT, 3:00 -->
- <timezone code="kw">Asia/Kuwait</timezone>
+ <timezone code="kw">Asia/Riyadh</timezone>
<!-- CAYMAN ISLANDS, -5:00 -->
- <timezone code="ky">America/Cayman</timezone>
+ <timezone code="ky">America/Panama</timezone>
<!-- KAZAKHSTAN, 6:00 -->
@@ -688,7 +713,7 @@
<!-- LAO PEOPLE'S DEMOCRATIC REPUBLIC, 7:00 -->
- <timezone code="la">Asia/Vientiane</timezone>
+ <timezone code="la">Asia/Bangkok</timezone>
<!-- LEBANON, 2:00 -->
@@ -696,11 +721,11 @@
<!-- SAINT LUCIA, -4:00 -->
- <timezone code="lc">America/St_Lucia</timezone>
+ <timezone code="lc">America/Port_of_Spain</timezone>
<!-- LIECHTENSTEIN, 1:00 -->
- <timezone code="li">Europe/Vaduz</timezone>
+ <timezone code="li">Europe/Zurich</timezone>
<!-- SRI LANKA, 5:30 -->
@@ -712,7 +737,7 @@
<!-- LESOTHO, 2:00 -->
- <timezone code="ls">Africa/Maseru</timezone>
+ <timezone code="ls">Africa/Johannesburg</timezone>
<!-- LITHUANIA, 2:00 -->
@@ -744,11 +769,15 @@
<!-- MONTENEGRO, 1:00 -->
- <timezone code="me">Europe/Podgorica</timezone>
+ <timezone code="me">Europe/Belgrade</timezone>
+
+ <!-- Collectivity of Saint Martin, -4:00 -->
+
+ <timezone code="mf">America/Port_of_Spain</timezone>
<!-- MADAGASCAR, 3:00 -->
- <timezone code="mg">Indian/Antananarivo</timezone>
+ <timezone code="mg">Africa/Nairobi</timezone>
<!-- MARSHALL ISLANDS, 12:00 -->
@@ -757,15 +786,15 @@
<!-- MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF, 1:00 -->
- <timezone code="mk">Europe/Skopje</timezone>
+ <timezone code="mk">Europe/Belgrade</timezone>
<!-- MALI, 0:00 -->
- <timezone code="ml">Africa/Bamako</timezone>
+ <timezone code="ml">Africa/Abidjan</timezone>
<!-- MYANMAR, 6:30 -->
- <timezone code="mm">Asia/Rangoon</timezone>
+ <timezone code="mm">Asia/Yangon</timezone>
<!-- MONGOLIA, 8:00 -->
@@ -782,7 +811,7 @@
<!-- NORTHERN MARIANA ISLANDS, 10:00 -->
- <timezone code="mp">Pacific/Saipan</timezone>
+ <timezone code="mp">Pacific/Guam</timezone>
<!-- MARTINIQUE, -4:00 -->
@@ -790,11 +819,11 @@
<!-- MAURITANIA, 0:00 -->
- <timezone code="mr">Africa/Nouakchott</timezone>
+ <timezone code="mr">Africa/Abidjan</timezone>
<!-- MONTSERRAT, -4:00 -->
- <timezone code="ms">America/Montserrat</timezone>
+ <timezone code="ms">America/Port_of_Spain</timezone>
<!-- MALTA, 1:00 -->
@@ -810,20 +839,26 @@
<!-- MALAWI, 2:00 -->
- <timezone code="mw">Africa/Blantyre</timezone>
+ <timezone code="mw">Africa/Maputo</timezone>
<!-- MEXICO, -6:00 -->
<timezone code="mx">America/Mexico_City</timezone>
- <timezone code="mx">America/Cancun</timezone>
<timezone code="mx">America/Merida</timezone>
<timezone code="mx">America/Monterrey</timezone>
+ <timezone code="mx">America/Matamoros</timezone>
+ <timezone code="mx">America/Bahia_Banderas</timezone>
+
+ <!-- MEXICO, -5:00 -->
+
+ <timezone code="mx">America/Cancun</timezone>
<!-- MEXICO, -7:00 -->
<timezone code="mx">America/Chihuahua</timezone>
<timezone code="mx">America/Hermosillo</timezone>
<timezone code="mx">America/Mazatlan</timezone>
+ <timezone code="mx">America/Ojinaga</timezone>
<!-- MEXICO, -8:00 -->
@@ -848,7 +883,7 @@
<!-- NIGER, 1:00 -->
- <timezone code="ne">Africa/Niamey</timezone>
+ <timezone code="ne">Africa/Lagos</timezone>
<!-- NORFOLK ISLAND, 11:30 -->
@@ -892,7 +927,7 @@
<!-- OMAN, 4:00 -->
- <timezone code="om">Asia/Muscat</timezone>
+ <timezone code="om">Asia/Dubai</timezone>
<!-- PANAMA, -5:00 -->
@@ -918,6 +953,10 @@
<timezone code="pg">Pacific/Port_Moresby</timezone>
+ <!-- PAPUA NEW GUINEA, 11:00 -->
+
+ <timezone code="pg">Pacific/Bougainville</timezone>
+
<!-- PHILIPPINES, 8:00 -->
<timezone code="ph">Asia/Manila</timezone>
@@ -945,6 +984,7 @@
<!-- PALESTINE, 2:00 -->
<timezone code="ps">Asia/Gaza</timezone>
+ <timezone code="ps">Asia/Hebron</timezone>
<!-- PORTUGAL, 0:00 -->
@@ -987,15 +1027,19 @@
<!-- RUSSIAN FEDERATION, 11:00 -->
<timezone code="ru">Asia/Magadan</timezone>
+ <timezone code="ru">Asia/Sakhalin</timezone>
+ <timezone code="ru">Asia/Srednekolymsk</timezone>
<!-- RUSSIAN FEDERATION, 10:00 -->
<timezone code="ru">Asia/Vladivostok</timezone>
- <timezone code="ru">Asia/Sakhalin</timezone>
+ <timezone code="ru">Asia/Ust-Nera</timezone>
<!-- RUSSIAN FEDERATION, 9:00 -->
<timezone code="ru">Asia/Yakutsk</timezone>
+ <timezone code="ru">Asia/Chita</timezone>
+ <timezone code="ru">Asia/Khandyga</timezone>
<!-- RUSSIAN FEDERATION, 8:00 -->
@@ -1004,10 +1048,13 @@
<!-- RUSSIAN FEDERATION, 7:00 -->
<timezone code="ru">Asia/Krasnoyarsk</timezone>
+ <timezone code="ru">Asia/Novosibirsk</timezone>
+ <timezone code="ru">Asia/Barnaul</timezone>
+ <timezone code="ru">Asia/Novokuznetsk</timezone>
+ <timezone code="ru">Asia/Tomsk</timezone>
<!-- RUSSIAN FEDERATION, 6:00 -->
- <timezone code="ru">Asia/Novosibirsk</timezone>
<timezone code="ru">Asia/Omsk</timezone>
<!-- RUSSIAN FEDERATION, 5:00 -->
@@ -1017,11 +1064,15 @@
<!-- RUSSIAN FEDERATION, 4:00 -->
<timezone code="ru">Europe/Samara</timezone>
+ <timezone code="ru">Europe/Astrakhan</timezone>
+ <timezone code="ru">Europe/Ulyanovsk</timezone>
<!-- RUSSIAN FEDERATION, 3:00 -->
<timezone code="ru">Europe/Moscow</timezone>
<timezone code="ru">Europe/Volgograd</timezone>
+ <timezone code="ru">Europe/Kirov</timezone>
+ <timezone code="ru">Europe/Simferopol</timezone>
<!-- RUSSIAN FEDERATION, 2:00 -->
@@ -1029,7 +1080,7 @@
<!-- RWANDA, 2:00 -->
- <timezone code="rw">Africa/Kigali</timezone>
+ <timezone code="rw">Africa/Maputo</timezone>
<!-- SAUDI ARABIA, 3:00 -->
@@ -1057,57 +1108,65 @@
<!-- SAINT HELENA, 0:00 -->
- <timezone code="sh">Atlantic/St_Helena</timezone>
+ <timezone code="sh">Africa/Abidjan</timezone>
<!-- SLOVENIA, 1:00 -->
- <timezone code="si">Europe/Ljubljana</timezone>
+ <timezone code="si">Europe/Belgrade</timezone>
<!-- SVALBARD AND JAN MAYEN, 1:00 -->
- <timezone code="sj">Arctic/Longyearbyen</timezone>
+ <timezone code="sj">Europe/Oslo</timezone>
<!-- SLOVAKIA, 1:00 -->
- <timezone code="sk">Europe/Bratislava</timezone>
+ <timezone code="sk">Europe/Prague</timezone>
<!-- SIERRA LEONE, 0:00 -->
- <timezone code="sl">Africa/Freetown</timezone>
+ <timezone code="sl">Africa/Abidjan</timezone>
<!-- SAN MARINO, 1:00 -->
- <timezone code="sm">Europe/San_Marino</timezone>
+ <timezone code="sm">Europe/Rome</timezone>
<!-- SENEGAL, 0:00 -->
- <timezone code="sn">Africa/Dakar</timezone>
+ <timezone code="sn">Africa/Abidjan</timezone>
<!-- SOMALIA, 3:00 -->
- <timezone code="so">Africa/Mogadishu</timezone>
+ <timezone code="so">Africa/Nairobi</timezone>
<!-- SURINAME, -3:00 -->
<timezone code="sr">America/Paramaribo</timezone>
+ <!-- South Sudan, 3:00 -->
+
+ <timezone code="ss">Africa/Khartoum</timezone>
+
<!-- SAO TOME AND PRINCIPE, 0:00 -->
- <timezone code="st">Africa/Sao_Tome</timezone>
+ <timezone code="st">Africa/Abidjan</timezone>
<!-- EL SALVADOR, -6:00 -->
<timezone code="sv">America/El_Salvador</timezone>
+ <!-- Sint Maarten, -4:00 -->
+
+ <timezone code="sx">America/Curacao</timezone>
+
<!-- SYRIAN ARAB REPUBLIC, 2:00 -->
<timezone code="sy">Asia/Damascus</timezone>
<!-- SWAZILAND, 2:00 -->
- <timezone code="sz">Africa/Mbabane</timezone>
+ <timezone code="sz">Africa/Johannesburg</timezone>
- <!-- TURKS AND CAICOS ISLANDS, -5:00 -->
+ <!-- TURKS AND CAICOS ISLANDS, -4:00 -->
<timezone code="tc">America/Grand_Turk</timezone>
@@ -1119,9 +1178,13 @@
<timezone code="tf">Indian/Kerguelen</timezone>
+ <!-- FRENCH SOUTHERN TERRITORIES, 4:00 -->
+
+ <timezone code="tf">Indian/Reunion</timezone>
+
<!-- TOGO, 0:00 -->
- <timezone code="tg">Africa/Lome</timezone>
+ <timezone code="tg">Africa/Abidjan</timezone>
<!-- THAILAND, 7:00 -->
@@ -1131,7 +1194,7 @@
<timezone code="tj">Asia/Dushanbe</timezone>
- <!-- TOKELAU, -10:00 -->
+ <!-- TOKELAU, +13:00 -->
<timezone code="tk">Pacific/Fakaofo</timezone>
@@ -1151,7 +1214,7 @@
<timezone code="to">Pacific/Tongatapu</timezone>
- <!-- TURKEY, 2:00 -->
+ <!-- TURKEY, 3:00 -->
<timezone code="tr">Europe/Istanbul</timezone>
@@ -1169,18 +1232,17 @@
<!-- TANZANIA, UNITED REPUBLIC OF, 3:00 -->
- <timezone code="tz">Africa/Dar_es_Salaam</timezone>
+ <timezone code="tz">Africa/Nairobi</timezone>
<!-- UKRAINE, 2:00 -->
<timezone code="ua">Europe/Kiev</timezone>
<timezone code="ua">Europe/Uzhgorod</timezone>
<timezone code="ua">Europe/Zaporozhye</timezone>
- <timezone code="ua">Europe/Simferopol</timezone>
<!-- UGANDA, 3:00 -->
- <timezone code="ug">Africa/Kampala</timezone>
+ <timezone code="ug">Africa/Nairobi</timezone>
<!-- UNITED STATES MINOR OUTLYING ISLANDS, 12:00 -->
@@ -1188,11 +1250,11 @@
<!-- UNITED STATES MINOR OUTLYING ISLANDS, -10:00 -->
- <timezone code="um">Pacific/Johnston</timezone>
+ <timezone code="um">Pacific/Honolulu</timezone>
<!-- UNITED STATES MINOR OUTLYING ISLANDS, -11:00 -->
- <timezone code="um">Pacific/Midway</timezone>
+ <timezone code="um">Pacific/Pago_Pago</timezone>
<!-- UNITED STATES, -5:00 -->
@@ -1214,12 +1276,13 @@
<timezone code="us">America/Menominee</timezone>
<timezone code="us">America/North_Dakota/Center</timezone>
<timezone code="us">America/North_Dakota/New_Salem</timezone>
+ <timezone code="us">America/Indiana/Tell_City</timezone>
+ <timezone code="us">America/North_Dakota/Beulah</timezone>
<!-- UNITED STATES, -7:00 -->
<timezone code="us">America/Denver</timezone>
<timezone code="us">America/Boise</timezone>
- <timezone code="us">America/Shiprock</timezone>
<timezone code="us">America/Phoenix</timezone>
<!-- UNITED STATES, -8:00 -->
@@ -1232,6 +1295,8 @@
<timezone code="us">America/Juneau</timezone>
<timezone code="us">America/Yakutat</timezone>
<timezone code="us">America/Nome</timezone>
+ <timezone code="us">America/Metlakatla</timezone>
+ <timezone code="us">America/Sitka</timezone>
<!-- UNITED STATES, -10:00 -->
@@ -1249,27 +1314,28 @@
<!-- HOLY SEE (VATICAN CITY STATE), 1:00 -->
- <timezone code="va">Europe/Vatican</timezone>
+ <timezone code="va">Europe/Rome</timezone>
<!-- SAINT VINCENT AND THE GRENADINES, -4:00 -->
- <timezone code="vc">America/St_Vincent</timezone>
+ <timezone code="vc">America/Port_of_Spain</timezone>
- <!-- VENEZUELA, -4:30 -->
+ <!-- VENEZUELA, -4:00 -->
<timezone code="ve">America/Caracas</timezone>
<!-- VIRGIN ISLANDS, BRITISH, -4:00 -->
- <timezone code="vg">America/Tortola</timezone>
+ <timezone code="vg">America/Port_of_Spain</timezone>
<!-- VIRGIN ISLANDS, U.S., -4:00 -->
- <timezone code="vi">America/St_Thomas</timezone>
+ <timezone code="vi">America/Port_of_Spain</timezone>
<!-- VIET NAM, 7:00 -->
- <timezone code="vn">Asia/Saigon</timezone>
+ <timezone code="vn">Asia/Ho_Chi_Minh</timezone>
+ <timezone code="vn">Asia/Bangkok</timezone>
<!-- VANUATU, 11:00 -->
@@ -1279,17 +1345,17 @@
<timezone code="wf">Pacific/Wallis</timezone>
- <!-- SAMOA, -11:00 -->
+ <!-- SAMOA, 13:00 -->
<timezone code="ws">Pacific/Apia</timezone>
<!-- YEMEN, 3:00 -->
- <timezone code="ye">Asia/Aden</timezone>
+ <timezone code="ye">Asia/Riyadh</timezone>
<!-- MAYOTTE, 3:00 -->
- <timezone code="yt">Indian/Mayotte</timezone>
+ <timezone code="yt">Africa/Nairobi</timezone>
<!-- SOUTH AFRICA, 2:00 -->
@@ -1297,9 +1363,9 @@
<!-- ZAMBIA, 2:00 -->
- <timezone code="zm">Africa/Lusaka</timezone>
+ <timezone code="zm">Africa/Maputo</timezone>
<!-- ZIMBABWE, 2:00 -->
- <timezone code="zw">Africa/Harare</timezone>
+ <timezone code="zw">Africa/Maputo</timezone>
</timezones>
diff --git a/core/tests/benchmarks/src/android/os/ParcelBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelBenchmark.java
index 4bd2d00..6c45ae8 100644
--- a/core/tests/benchmarks/src/android/os/ParcelBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelBenchmark.java
@@ -20,12 +20,15 @@
import com.google.caliper.BeforeExperiment;
public class ParcelBenchmark {
+ private static final int INNER_REPS = 1000;
private Parcel mParcel;
@BeforeExperiment
protected void setUp() {
mParcel = Parcel.obtain();
+ mParcel.setDataPosition(0);
+ mParcel.setDataCapacity(INNER_REPS * 8);
}
@AfterExperiment
@@ -36,43 +39,58 @@
public void timeWriteByte(int reps) {
final byte val = 0xF;
- for (int i = 0; i < reps; i++) {
- mParcel.writeByte(val);
+ for (int i = 0; i < (reps / INNER_REPS); i++) {
+ mParcel.setDataPosition(0);
+ for (int j = 0; j < INNER_REPS; j++) {
+ mParcel.writeByte(val);
+ }
}
}
public void timeReadByte(int reps) {
- mParcel.setDataCapacity(reps);
- for (int i = 0; i < reps; i++) {
- mParcel.readByte();
+ for (int i = 0; i < (reps / INNER_REPS); i++) {
+ mParcel.setDataPosition(0);
+ for (int j = 0; j < INNER_REPS; j++) {
+ mParcel.readByte();
+ }
}
}
public void timeWriteInt(int reps) {
final int val = 0xF;
- for (int i = 0; i < reps; i++) {
- mParcel.writeInt(val);
+ for (int i = 0; i < (reps / INNER_REPS); i++) {
+ mParcel.setDataPosition(0);
+ for (int j = 0; j < INNER_REPS; j++) {
+ mParcel.writeInt(val);
+ }
}
}
public void timeReadInt(int reps) {
- mParcel.setDataCapacity(reps << 2);
- for (int i = 0; i < reps; i++) {
- mParcel.readInt();
+ for (int i = 0; i < (reps / INNER_REPS); i++) {
+ mParcel.setDataPosition(0);
+ for (int j = 0; j < INNER_REPS; j++) {
+ mParcel.readInt();
+ }
}
}
public void timeWriteLong(int reps) {
final long val = 0xF;
- for (int i = 0; i < reps; i++) {
- mParcel.writeLong(val);
+ for (int i = 0; i < (reps / INNER_REPS); i++) {
+ mParcel.setDataPosition(0);
+ for (int j = 0; j < INNER_REPS; j++) {
+ mParcel.writeLong(val);
+ }
}
}
public void timeReadLong(int reps) {
- mParcel.setDataCapacity(reps << 3);
- for (int i = 0; i < reps; i++) {
- mParcel.readLong();
+ for (int i = 0; i < (reps / INNER_REPS); i++) {
+ mParcel.setDataPosition(0);
+ for (int j = 0; j < INNER_REPS; j++) {
+ mParcel.readLong();
+ }
}
}
}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c234b6a..ba1a55d 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1323,12 +1323,6 @@
</meta-data>
</service>
- <activity
- android:name="android.print.mockservice.SettingsActivity"
- android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
- android:exported="true">
- </activity>
-
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/res/xml/printservice.xml b/core/tests/coretests/res/xml/printservice.xml
index abbebda..3bd2b04 100644
--- a/core/tests/coretests/res/xml/printservice.xml
+++ b/core/tests/coretests/res/xml/printservice.xml
@@ -16,5 +16,4 @@
limitations under the License.
-->
-<print-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:settingsActivity="android.print.mockservice.SettingsActivity"/>
+<print-service xmlns:android="http://schemas.android.com/apk/res/android" />
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/print/BasePrintTest.java b/core/tests/coretests/src/android/print/BasePrintTest.java
index 2c2ee8c..8ef8062 100644
--- a/core/tests/coretests/src/android/print/BasePrintTest.java
+++ b/core/tests/coretests/src/android/print/BasePrintTest.java
@@ -16,6 +16,9 @@
package android.print;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
@@ -26,173 +29,134 @@
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.os.CancellationSignal;
-import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
-import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintManager;
-import android.print.PrinterId;
import android.print.mockservice.PrintServiceCallbacks;
import android.print.mockservice.PrinterDiscoverySessionCallbacks;
import android.print.mockservice.StubbablePrinterDiscoverySession;
import android.printservice.CustomPrinterIconCallback;
import android.printservice.PrintJob;
import android.printservice.PrintService;
-import android.test.InstrumentationTestCase;
-import android.util.DisplayMetrics;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.rule.ActivityTestRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
import org.mockito.stubbing.Answer;
-import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Locale;
import java.util.concurrent.TimeoutException;
/**
* This is the base class for print tests.
*/
-public abstract class BasePrintTest extends InstrumentationTestCase {
-
- private static final long OPERATION_TIMEOUT = 30000;
+abstract class BasePrintTest {
+ protected static final long OPERATION_TIMEOUT = 30000;
private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
- private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
- private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
- private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
- private PrintTestActivity mActivity;
private android.print.PrintJob mPrintJob;
- private LocaleList mOldLocale;
-
private CallCounter mStartCallCounter;
private CallCounter mStartSessionCallCounter;
- private String[] mEnabledImes;
+ private static Instrumentation sInstrumentation;
+ private static UiDevice sUiDevice;
- private String[] getEnabledImes() throws IOException {
- List<String> imeList = new ArrayList<>();
+ @Rule
+ public ActivityTestRule<PrintTestActivity> mActivityRule =
+ new ActivityTestRule<>(PrintTestActivity.class, false, true);
- ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
- .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
+ /**
+ * {@link Runnable} that can throw and {@link Exception}
+ */
+ interface Invokable {
+ /**
+ * Execute the invokable
+ *
+ * @throws Exception
+ */
+ void run() throws Exception;
+ }
- String line;
- while ((line = reader.readLine()) != null) {
- imeList.add(line);
+ /**
+ * Assert that the invokable throws an expectedException
+ *
+ * @param invokable The {@link Invokable} to run
+ * @param expectedClass The {@link Exception} that is supposed to be thrown
+ */
+ void assertException(Invokable invokable, Class<? extends Exception> expectedClass)
+ throws Exception {
+ try {
+ invokable.run();
+ } catch (Exception e) {
+ if (e.getClass().isAssignableFrom(expectedClass)) {
+ return;
+ } else {
+ throw e;
}
}
- String[] imeArray = new String[imeList.size()];
- imeList.toArray(imeArray);
-
- return imeArray;
+ throw new AssertionError("No exception thrown");
}
- private void disableImes() throws Exception {
- mEnabledImes = getEnabledImes();
- for (String ime : mEnabledImes) {
- String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
- runShellCommand(getInstrumentation(), disableImeCommand);
- }
+ /**
+ * Return the UI device
+ *
+ * @return the UI device
+ */
+ public UiDevice getUiDevice() {
+ return sUiDevice;
}
- private void enableImes() throws Exception {
- for (String ime : mEnabledImes) {
- String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
- runShellCommand(getInstrumentation(), enableImeCommand);
- }
- mEnabledImes = null;
+ protected static Instrumentation getInstrumentation() {
+ return sInstrumentation;
}
- @Override
- protected void runTest() throws Throwable {
- // Do nothing if the device does not support printing.
- if (supportsPrinting()) {
- super.runTest();
- }
- }
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ assumeTrue(sInstrumentation.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_PRINTING));
- @Override
- public void setUp() throws Exception {
- super.setUp();
- if (!supportsPrinting()) {
- return;
- }
+ sUiDevice = UiDevice.getInstance(sInstrumentation);
// Make sure we start with a clean slate.
clearPrintSpoolerData();
- disableImes();
// Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
// Dexmaker is used by mockito.
System.setProperty("dexmaker.dexcache", getInstrumentation()
.getTargetContext().getCacheDir().getPath());
+ }
- // Set to US locale.
- Resources resources = getInstrumentation().getTargetContext().getResources();
- Configuration oldConfiguration = resources.getConfiguration();
- if (!oldConfiguration.getLocales().get(0).equals(Locale.US)) {
- mOldLocale = oldConfiguration.getLocales();
- DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- Configuration newConfiguration = new Configuration(oldConfiguration);
- newConfiguration.setLocale(Locale.US);
- resources.updateConfiguration(newConfiguration, displayMetrics);
- }
-
+ @Before
+ public void initCounters() throws Exception {
// Initialize the latches.
mStartCallCounter = new CallCounter();
mStartSessionCallCounter = new CallCounter();
-
- // Create the activity for the right locale.
- createActivity();
}
- @Override
- public void tearDown() throws Exception {
- if (!supportsPrinting()) {
- return;
- }
-
- // Done with the activity.
- getActivity().finish();
- enableImes();
-
- // Restore the locale if needed.
- if (mOldLocale != null) {
- Resources resources = getInstrumentation().getTargetContext().getResources();
- DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- Configuration newConfiguration = new Configuration(resources.getConfiguration());
- newConfiguration.setLocales(mOldLocale);
- mOldLocale = null;
- resources.updateConfiguration(newConfiguration, displayMetrics);
- }
-
- // Make sure the spooler is cleaned, this also un-approves all services
- clearPrintSpoolerData();
-
- super.tearDown();
+ @After
+ public void exitActivities() throws Exception {
+ // Exit print spooler
+ getUiDevice().pressBack();
+ getUiDevice().pressBack();
}
protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
final PrintAttributes attributes) {
// Initiate printing as if coming from the app.
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- PrintManager printManager = (PrintManager) getActivity()
- .getSystemService(Context.PRINT_SERVICE);
- mPrintJob = printManager.print("Print job", adapter, attributes);
- }
+ getInstrumentation().runOnMainSync(() -> {
+ PrintManager printManager = (PrintManager) getActivity()
+ .getSystemService(Context.PRINT_SERVICE);
+ mPrintJob = printManager.print("Print job", adapter, attributes);
});
return mPrintJob;
@@ -215,7 +179,7 @@
waitForCallbackCallCount(mStartCallCounter, 1, "Did not get expected call to start.");
}
- private void waitForCallbackCallCount(CallCounter counter, int count, String message) {
+ private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
try {
counter.waitForCount(count, OPERATION_TIMEOUT);
} catch (TimeoutException te) {
@@ -224,12 +188,7 @@
}
protected PrintTestActivity getActivity() {
- return mActivity;
- }
-
- protected void createActivity() {
- mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
- PrintTestActivity.class, null);
+ return mActivityRule.getActivity();
}
public static String runShellCommand(Instrumentation instrumentation, String cmd)
@@ -238,7 +197,7 @@
byte[] buf = new byte[512];
int bytesRead;
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
- StringBuffer stdout = new StringBuffer();
+ StringBuilder stdout = new StringBuilder();
while ((bytesRead = fis.read(buf)) != -1) {
stdout.append(new String(buf, 0, bytesRead));
}
@@ -246,7 +205,7 @@
return stdout.toString();
}
- protected void clearPrintSpoolerData() throws Exception {
+ protected static void clearPrintSpoolerData() throws Exception {
assertTrue("failed to clear print spooler data",
runShellCommand(getInstrumentation(), String.format(
"pm clear --user %d %s", CURRENT_USER_ID,
@@ -319,7 +278,7 @@
return service;
}
- protected final class CallCounter {
+ private static final class CallCounter {
private final Object mLock = new Object();
private int mCallCount;
@@ -331,7 +290,7 @@
}
}
- public int getCallCount() {
+ int getCallCount() {
synchronized (mLock) {
return mCallCount;
}
@@ -355,9 +314,4 @@
}
}
}
-
- protected boolean supportsPrinting() {
- return getInstrumentation().getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_PRINTING);
- }
}
diff --git a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
index d491ec4..2e9c8e7 100644
--- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
+++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
@@ -38,17 +38,23 @@
import android.print.mockservice.PrinterDiscoverySessionCallbacks;
import android.print.mockservice.StubbablePrinterDiscoverySession;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
/**
* tests feeding all possible parameters to the IPrintManager Binder.
*/
+@RunWith(AndroidJUnit4.class)
public class IPrintManagerParametersTest extends BasePrintTest {
private final int BAD_APP_ID = 0xffffffff;
@@ -58,9 +64,7 @@
private final PrintJobId mBadPrintJobId;
private PrintJob mGoodPrintJob;
- private PrinterId mBadPrinterId;
private PrinterId mGoodPrinterId;
- private ComponentName mGoodComponentName;
private ComponentName mBadComponentName;
private IPrintManager mIPrintManager;
@@ -93,6 +97,7 @@
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
CancellationSignal cancellationSignal, LayoutResultCallback callback,
Bundle extras) {
+ callback.onLayoutFailed("not implemented");
}
@Override
@@ -109,54 +114,46 @@
*/
private PrintServiceCallbacks createMockCallbacks() {
return createMockPrintServiceCallbacks(
- new Answer<PrinterDiscoverySessionCallbacks>() {
- @Override
- public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
- return createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) {
- // Get the session.
- StubbablePrinterDiscoverySession session =
- ((PrinterDiscoverySessionCallbacks) invocation
- .getMock()).getSession();
+ invocation -> createMockPrinterDiscoverySessionCallbacks(invocation1 -> {
+ // Get the session.
+ StubbablePrinterDiscoverySession session =
+ ((PrinterDiscoverySessionCallbacks) invocation1
+ .getMock()).getSession();
- if (session.getPrinters().isEmpty()) {
- final String PRINTER_NAME = "good printer";
- List<PrinterInfo> printers = new ArrayList<>();
+ if (session.getPrinters().isEmpty()) {
+ final String PRINTER_NAME = "good printer";
+ List<PrinterInfo> printers = new ArrayList<>();
- // Add the printer.
- mGoodPrinterId = session.getService()
- .generatePrinterId(PRINTER_NAME);
+ // Add the printer.
+ mGoodPrinterId = session.getService()
+ .generatePrinterId(PRINTER_NAME);
- PrinterCapabilitiesInfo capabilities =
- new PrinterCapabilitiesInfo.Builder(mGoodPrinterId)
- .setMinMargins(
- new Margins(200, 200, 200, 200))
- .addMediaSize(MediaSize.ISO_A4, true)
- .addResolution(new Resolution("300x300",
- "300x300", 300, 300),
- true)
- .setColorModes(
- PrintAttributes.COLOR_MODE_COLOR,
- PrintAttributes.COLOR_MODE_COLOR)
- .build();
+ PrinterCapabilitiesInfo capabilities =
+ new PrinterCapabilitiesInfo.Builder(mGoodPrinterId)
+ .setMinMargins(
+ new Margins(200, 200, 200, 200))
+ .addMediaSize(MediaSize.ISO_A4, true)
+ .addResolution(new Resolution("300x300",
+ "300x300", 300, 300),
+ true)
+ .setColorModes(
+ PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .build();
- PrinterInfo printer = new PrinterInfo.Builder(
- mGoodPrinterId,
- PRINTER_NAME,
- PrinterInfo.STATUS_IDLE)
- .setCapabilities(capabilities)
- .build();
- printers.add(printer);
+ PrinterInfo printer = new PrinterInfo.Builder(
+ mGoodPrinterId,
+ PRINTER_NAME,
+ PrinterInfo.STATUS_IDLE)
+ .setCapabilities(capabilities)
+ .build();
+ printers.add(printer);
- session.addPrinters(printers);
- }
- onPrinterDiscoverySessionStartCalled();
- return null;
- }
- }, null, null, null, null, null, null);
+ session.addPrinters(printers);
}
- },
+ onPrinterDiscoverySessionStartCalled();
+ return null;
+ }, null, null, null, null, null, null),
null, null);
}
@@ -214,60 +211,28 @@
waitForPrinterDiscoverySessionStartCallbackCalled();
}
- @Override
- public void setUp() throws Exception {
- super.setUp();
+ /**
+ * Return a printer Id that is not from any print service
+ *
+ * @return The bad printer id.
+ */
+ private PrinterId getBadPrinterId() {
+ return new PrinterId(getActivity().getComponentName(), "dummy printer");
+ }
+ @Before
+ public void setUpMockService() throws Exception {
MockPrintService.setCallbacks(createMockCallbacks());
- mGoodComponentName = getActivity().getComponentName();
-
mIPrintManager = IPrintManager.Stub
.asInterface(ServiceManager.getService(Context.PRINT_SERVICE));
-
- // Generate dummy printerId which is a valid PrinterId object, but does not correspond to a
- // printer
- mBadPrinterId = new PrinterId(mGoodComponentName, "dummy printer");
- }
-
- /**
- * {@link Runnable} that can throw and {@link Exception}
- */
- private interface Invokable {
- /**
- * Execute the invokable
- *
- * @throws Exception
- */
- void run() throws Exception;
- }
-
- /**
- * Assert that the invokable throws an expectedException
- *
- * @param invokable The {@link Invokable} to run
- * @param expectedClass The {@link Exception} that is supposed to be thrown
- */
- public void assertException(Invokable invokable, Class<? extends Exception> expectedClass)
- throws Exception {
- try {
- invokable.run();
- } catch (Exception e) {
- if (e.getClass().isAssignableFrom(expectedClass)) {
- return;
- } else {
- throw new AssertionError("Expected: " + expectedClass.getName() + ", got: "
- + e.getClass().getName());
- }
- }
-
- throw new AssertionError("No exception thrown");
}
/**
* test IPrintManager.getPrintJobInfo
*/
@LargeTest
+ @Test
public void testGetPrintJobInfo() throws Exception {
startPrinting();
@@ -276,12 +241,9 @@
assertEquals(null, mIPrintManager.getPrintJobInfo(mBadPrintJobId, mAppId, mUserId));
assertEquals(null, mIPrintManager.getPrintJobInfo(null, mAppId, mUserId));
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.getPrintJobInfo(mGoodPrintJob.getId(), BAD_APP_ID, mUserId);
- }
- }, SecurityException.class);
+ assertException(
+ () -> mIPrintManager.getPrintJobInfo(mGoodPrintJob.getId(), BAD_APP_ID, mUserId),
+ SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -290,6 +252,7 @@
* test IPrintManager.getPrintJobInfos
*/
@LargeTest
+ @Test
public void testGetPrintJobInfos() throws Exception {
startPrinting();
@@ -304,12 +267,8 @@
}
assertTrue(foundPrintJob);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.getPrintJobInfos(BAD_APP_ID, mUserId);
- }
- }, SecurityException.class);
+ assertException(() -> mIPrintManager.getPrintJobInfos(BAD_APP_ID, mUserId),
+ SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -318,6 +277,7 @@
* test IPrintManager.print
*/
@LargeTest
+ @Test
public void testPrint() throws Exception {
final String name = "dummy print job";
@@ -326,44 +286,23 @@
startPrinting();
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.print(null, adapter, null, mGoodComponentName.getPackageName(),
- mAppId, mUserId);
- }
- }, IllegalArgumentException.class);
+ assertException(() -> mIPrintManager.print(null, adapter, null,
+ getActivity().getPackageName(), mAppId, mUserId),
+ IllegalArgumentException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.print(name, null, null, mGoodComponentName.getPackageName(),
- mAppId, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.print(name, null, null,
+ getActivity().getPackageName(), mAppId, mUserId),
+ NullPointerException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.print(name, adapter, null, null, mAppId, mUserId);
- }
- }, IllegalArgumentException.class);
+ assertException(() -> mIPrintManager.print(name, adapter, null, null, mAppId, mUserId),
+ IllegalArgumentException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.print(name, adapter, null, mBadComponentName.getPackageName(),
- mAppId, mUserId);
- }
- }, IllegalArgumentException.class);
+ assertException(() -> mIPrintManager.print(name, adapter, null,
+ mBadComponentName.getPackageName(), mAppId, mUserId),
+ IllegalArgumentException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.print(name, adapter, null, mGoodComponentName.getPackageName(),
- BAD_APP_ID, mUserId);
- }
- }, SecurityException.class);
+ assertException(() -> mIPrintManager.print(name, adapter, null,
+ getActivity().getPackageName(), BAD_APP_ID, mUserId), SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -372,6 +311,7 @@
* test IPrintManager.cancelPrintJob
*/
@LargeTest
+ @Test
public void testCancelPrintJob() throws Exception {
startPrinting();
@@ -379,12 +319,9 @@
mIPrintManager.cancelPrintJob(mBadPrintJobId, mAppId, mUserId);
mIPrintManager.cancelPrintJob(null, mAppId, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.cancelPrintJob(mGoodPrintJob.getId(), BAD_APP_ID, mUserId);
- }
- }, SecurityException.class);
+ assertException(
+ () -> mIPrintManager.cancelPrintJob(mGoodPrintJob.getId(), BAD_APP_ID, mUserId),
+ SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
@@ -396,6 +333,7 @@
* test IPrintManager.restartPrintJob
*/
@LargeTest
+ @Test
public void testRestartPrintJob() throws Exception {
startPrinting();
@@ -405,12 +343,9 @@
mIPrintManager.restartPrintJob(mBadPrintJobId, mAppId, mUserId);
mIPrintManager.restartPrintJob(null, mAppId, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.restartPrintJob(mGoodPrintJob.getId(), BAD_APP_ID, mUserId);
- }
- }, SecurityException.class);
+ assertException(
+ () -> mIPrintManager.restartPrintJob(mGoodPrintJob.getId(), BAD_APP_ID, mUserId),
+ SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -419,24 +354,18 @@
* test IPrintManager.addPrintJobStateChangeListener
*/
@MediumTest
+ @Test
public void testAddPrintJobStateChangeListener() throws Exception {
final IPrintJobStateChangeListener listener = createMockIPrintJobStateChangeListener();
mIPrintManager.addPrintJobStateChangeListener(listener, mAppId, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.addPrintJobStateChangeListener(null, mAppId, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.addPrintJobStateChangeListener(null, mAppId, mUserId),
+ NullPointerException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.addPrintJobStateChangeListener(listener, BAD_APP_ID, mUserId);
- }
- }, SecurityException.class);
+ assertException(
+ () -> mIPrintManager.addPrintJobStateChangeListener(listener, BAD_APP_ID, mUserId),
+ SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -445,6 +374,7 @@
* test IPrintManager.removePrintJobStateChangeListener
*/
@MediumTest
+ @Test
public void testRemovePrintJobStateChangeListener() throws Exception {
final IPrintJobStateChangeListener listener = createMockIPrintJobStateChangeListener();
@@ -455,12 +385,8 @@
mIPrintManager.removePrintJobStateChangeListener(listener, mUserId);
mIPrintManager.addPrintJobStateChangeListener(listener, mAppId, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.removePrintJobStateChangeListener(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.removePrintJobStateChangeListener(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -469,17 +395,14 @@
* test IPrintManager.addPrintServicesChangeListener
*/
@MediumTest
+ @Test
public void testAddPrintServicesChangeListener() throws Exception {
final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener();
mIPrintManager.addPrintServicesChangeListener(listener, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.addPrintServicesChangeListener(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.addPrintServicesChangeListener(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -488,6 +411,7 @@
* test IPrintManager.removePrintServicesChangeListener
*/
@MediumTest
+ @Test
public void testRemovePrintServicesChangeListener() throws Exception {
final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener();
@@ -498,12 +422,8 @@
mIPrintManager.removePrintServicesChangeListener(listener, mUserId);
mIPrintManager.addPrintServicesChangeListener(listener, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.removePrintServicesChangeListener(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.removePrintServicesChangeListener(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -512,6 +432,7 @@
* test IPrintManager.getPrintServices
*/
@MediumTest
+ @Test
public void testGetPrintServices() throws Exception {
List<PrintServiceInfo> printServices = mIPrintManager.getPrintServices(
PrintManager.ALL_SERVICES, mUserId);
@@ -520,12 +441,8 @@
printServices = mIPrintManager.getPrintServices(0, mUserId);
assertEquals(printServices, null);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.getPrintServices(~PrintManager.ALL_SERVICES, mUserId);
- }
- }, IllegalArgumentException.class);
+ assertException(() -> mIPrintManager.getPrintServices(~PrintManager.ALL_SERVICES, mUserId),
+ IllegalArgumentException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -534,38 +451,23 @@
* test IPrintManager.setPrintServiceEnabled
*/
@MediumTest
+ @Test
public void testSetPrintServiceEnabled() throws Exception {
final ComponentName printService = mIPrintManager.getPrintServices(
PrintManager.ALL_SERVICES, mUserId).get(0).getComponentName();
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.setPrintServiceEnabled(printService, false, mUserId);
- }
- }, SecurityException.class);
+ assertException(() -> mIPrintManager.setPrintServiceEnabled(printService, false, mUserId),
+ SecurityException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.setPrintServiceEnabled(printService, true, mUserId);
- }
- }, SecurityException.class);
+ assertException(() -> mIPrintManager.setPrintServiceEnabled(printService, true, mUserId),
+ SecurityException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.setPrintServiceEnabled(new ComponentName("bad", "name"), true,
- mUserId);
- }
- }, SecurityException.class);
+ assertException(
+ () -> mIPrintManager.setPrintServiceEnabled(new ComponentName("bad", "name"), true,
+ mUserId), SecurityException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.setPrintServiceEnabled(null, true, mUserId);
- }
- }, SecurityException.class);
+ assertException(() -> mIPrintManager.setPrintServiceEnabled(null, true, mUserId),
+ SecurityException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -574,18 +476,16 @@
* test IPrintManager.addPrintServiceRecommendationsChangeListener
*/
@MediumTest
+ @Test
public void testAddPrintServiceRecommendationsChangeListener() throws Exception {
final IRecommendationsChangeListener listener =
createMockIPrintServiceRecommendationsChangeListener();
mIPrintManager.addPrintServiceRecommendationsChangeListener(listener, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.addPrintServiceRecommendationsChangeListener(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(
+ () -> mIPrintManager.addPrintServiceRecommendationsChangeListener(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -594,6 +494,7 @@
* test IPrintManager.removePrintServicesChangeListener
*/
@MediumTest
+ @Test
public void testRemovePrintServiceRecommendationsChangeListener() throws Exception {
final IRecommendationsChangeListener listener =
createMockIPrintServiceRecommendationsChangeListener();
@@ -605,12 +506,9 @@
mIPrintManager.removePrintServiceRecommendationsChangeListener(listener, mUserId);
mIPrintManager.addPrintServiceRecommendationsChangeListener(listener, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.removePrintServiceRecommendationsChangeListener(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(
+ () -> mIPrintManager.removePrintServiceRecommendationsChangeListener(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -619,6 +517,7 @@
* test IPrintManager.getPrintServiceRecommendations
*/
@MediumTest
+ @Test
public void testGetPrintServiceRecommendations() throws Exception {
mIPrintManager.getPrintServiceRecommendations(mUserId);
@@ -629,18 +528,15 @@
* test IPrintManager.createPrinterDiscoverySession
*/
@MediumTest
+ @Test
public void testCreatePrinterDiscoverySession() throws Exception {
final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
mIPrintManager.createPrinterDiscoverySession(listener, mUserId);
try {
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.createPrinterDiscoverySession(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.createPrinterDiscoverySession(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
} finally {
@@ -655,6 +551,7 @@
* test IPrintManager.startPrinterDiscovery
*/
@LargeTest
+ @Test
public void testStartPrinterDiscovery() throws Exception {
startPrinting();
@@ -663,7 +560,7 @@
goodPrinters.add(mGoodPrinterId);
final List<PrinterId> badPrinters = new ArrayList<>();
- badPrinters.add(mBadPrinterId);
+ badPrinters.add(getBadPrinterId());
final List<PrinterId> emptyPrinters = new ArrayList<>();
@@ -677,19 +574,11 @@
mIPrintManager.startPrinterDiscovery(listener, emptyPrinters, mUserId);
mIPrintManager.startPrinterDiscovery(listener, null, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.startPrinterDiscovery(listener, nullPrinters, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.startPrinterDiscovery(listener, nullPrinters, mUserId),
+ NullPointerException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.startPrinterDiscovery(null, goodPrinters, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.startPrinterDiscovery(null, goodPrinters, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -698,6 +587,7 @@
* test IPrintManager.stopPrinterDiscovery
*/
@MediumTest
+ @Test
public void testStopPrinterDiscovery() throws Exception {
final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
@@ -708,12 +598,8 @@
mIPrintManager.stopPrinterDiscovery(listener, mUserId);
mIPrintManager.startPrinterDiscovery(listener, null, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.stopPrinterDiscovery(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.stopPrinterDiscovery(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -722,6 +608,7 @@
* test IPrintManager.validatePrinters
*/
@LargeTest
+ @Test
public void testValidatePrinters() throws Exception {
startPrinting();
@@ -729,7 +616,7 @@
goodPrinters.add(mGoodPrinterId);
final List<PrinterId> badPrinters = new ArrayList<>();
- badPrinters.add(mBadPrinterId);
+ badPrinters.add(getBadPrinterId());
final List<PrinterId> emptyPrinters = new ArrayList<>();
@@ -742,19 +629,11 @@
mIPrintManager.validatePrinters(badPrinters, mUserId);
mIPrintManager.validatePrinters(emptyPrinters, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.validatePrinters(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.validatePrinters(null, mUserId),
+ NullPointerException.class);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.validatePrinters(nullPrinters, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.validatePrinters(nullPrinters, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -763,20 +642,17 @@
* test IPrintManager.startPrinterStateTracking
*/
@LargeTest
+ @Test
public void testStartPrinterStateTracking() throws Exception {
startPrinting();
mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId);
// Bad printers do no cause exceptions
- mIPrintManager.startPrinterStateTracking(mBadPrinterId, mUserId);
+ mIPrintManager.startPrinterStateTracking(getBadPrinterId(), mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.startPrinterStateTracking(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.startPrinterStateTracking(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -785,20 +661,17 @@
* test IPrintManager.getCustomPrinterIcon
*/
@LargeTest
+ @Test
public void testGetCustomPrinterIcon() throws Exception {
startPrinting();
mIPrintManager.getCustomPrinterIcon(mGoodPrinterId, mUserId);
// Bad printers do no cause exceptions
- mIPrintManager.getCustomPrinterIcon(mBadPrinterId, mUserId);
+ mIPrintManager.getCustomPrinterIcon(getBadPrinterId(), mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.getCustomPrinterIcon(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.getCustomPrinterIcon(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -807,6 +680,7 @@
* test IPrintManager.stopPrinterStateTracking
*/
@LargeTest
+ @Test
public void testStopPrinterStateTracking() throws Exception {
startPrinting();
@@ -817,15 +691,11 @@
mIPrintManager.stopPrinterStateTracking(mGoodPrinterId, mUserId);
// Bad printers do no cause exceptions
- mIPrintManager.startPrinterStateTracking(mBadPrinterId, mUserId);
- mIPrintManager.stopPrinterStateTracking(mBadPrinterId, mUserId);
+ mIPrintManager.startPrinterStateTracking(getBadPrinterId(), mUserId);
+ mIPrintManager.stopPrinterStateTracking(getBadPrinterId(), mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.stopPrinterStateTracking(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.stopPrinterStateTracking(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
@@ -834,6 +704,7 @@
* test IPrintManager.destroyPrinterDiscoverySession
*/
@MediumTest
+ @Test
public void testDestroyPrinterDiscoverySession() throws Exception {
final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
@@ -843,12 +714,8 @@
// Destroying already destroyed session is a no-op
mIPrintManager.destroyPrinterDiscoverySession(listener, mUserId);
- assertException(new Invokable() {
- @Override
- public void run() throws Exception {
- mIPrintManager.destroyPrinterDiscoverySession(null, mUserId);
- }
- }, NullPointerException.class);
+ assertException(() -> mIPrintManager.destroyPrinterDiscoverySession(null, mUserId),
+ NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
diff --git a/core/tests/coretests/src/android/print/PrintTestActivity.java b/core/tests/coretests/src/android/print/PrintTestActivity.java
index 86074a6..e9b001f 100644
--- a/core/tests/coretests/src/android/print/PrintTestActivity.java
+++ b/core/tests/coretests/src/android/print/PrintTestActivity.java
@@ -21,7 +21,6 @@
import android.view.WindowManager;
public class PrintTestActivity extends Activity {
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
index e132d79..f3a5373 100644
--- a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
+++ b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
@@ -16,6 +16,7 @@
package android.print.mockservice;
+import android.support.annotation.NonNull;
import android.os.CancellationSignal;
import android.print.PrinterId;
import android.printservice.CustomPrinterIconCallback;
@@ -42,7 +43,7 @@
}
@Override
- public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
+ public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
if (mCallbacks != null) {
mCallbacks.onStartPrinterDiscovery(priorityList);
}
@@ -56,29 +57,30 @@
}
@Override
- public void onValidatePrinters(List<PrinterId> printerIds) {
+ public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
if (mCallbacks != null) {
mCallbacks.onValidatePrinters(printerIds);
}
}
@Override
- public void onStartPrinterStateTracking(PrinterId printerId) {
+ public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
if (mCallbacks != null) {
mCallbacks.onStartPrinterStateTracking(printerId);
}
}
@Override
- public void onRequestCustomPrinterIcon(PrinterId printerId,
- CancellationSignal cancellationSignal, CustomPrinterIconCallback callback) {
+ public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull CustomPrinterIconCallback callback) {
if (mCallbacks != null) {
mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
}
}
@Override
- public void onStopPrinterStateTracking(PrinterId printerId) {
+ public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
if (mCallbacks != null) {
mCallbacks.onStopPrinterStateTracking(printerId);
}
diff --git a/core/tests/coretests/src/android/provider/DocumentsProviderTest.java b/core/tests/coretests/src/android/provider/DocumentsProviderTest.java
new file mode 100644
index 0000000..0b4675c
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/DocumentsProviderTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.provider;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.DocumentsContract.Path;
+import android.support.test.filters.SmallTest;
+import android.test.ProviderTestCase2;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DocumentsProvider}.
+ */
+@SmallTest
+public class DocumentsProviderTest extends ProviderTestCase2<TestDocumentsProvider> {
+
+ private static final String ROOT_ID = "rootId";
+ private static final String DOCUMENT_ID = "docId";
+ private static final String PARENT_DOCUMENT_ID = "parentDocId";
+ private static final String ANCESTOR_DOCUMENT_ID = "ancestorDocId";
+
+ private TestDocumentsProvider mProvider;
+
+ private ContentResolver mResolver;
+
+ public DocumentsProviderTest() {
+ super(TestDocumentsProvider.class, TestDocumentsProvider.AUTHORITY);
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mProvider = getProvider();
+ mResolver = getMockContentResolver();
+ }
+
+ public void testFindPath_docUri() throws Exception {
+ final Path expected = new Path(ROOT_ID, Arrays.asList(PARENT_DOCUMENT_ID, DOCUMENT_ID));
+ mProvider.nextPath = expected;
+
+ final Uri docUri =
+ DocumentsContract.buildDocumentUri(TestDocumentsProvider.AUTHORITY, DOCUMENT_ID);
+ try (ContentProviderClient client =
+ mResolver.acquireUnstableContentProviderClient(docUri)) {
+ final Path actual = DocumentsContract.findPath(client, docUri);
+ assertEquals(expected, actual);
+ }
+ }
+
+ public void testFindPath_treeUri() throws Exception {
+ mProvider.nextIsChildDocument = true;
+
+ final Path expected = new Path(null, Arrays.asList(PARENT_DOCUMENT_ID, DOCUMENT_ID));
+ mProvider.nextPath = expected;
+
+ final Uri docUri = buildTreeDocumentUri(
+ TestDocumentsProvider.AUTHORITY, PARENT_DOCUMENT_ID, DOCUMENT_ID);
+ final List<String> actual = DocumentsContract.findPath(mResolver, docUri);
+
+ assertEquals(expected.getPath(), actual);
+ }
+
+ public void testFindPath_treeUri_throwsOnNonChildDocument() throws Exception {
+ mProvider.nextPath = new Path(null, Arrays.asList(PARENT_DOCUMENT_ID, DOCUMENT_ID));
+
+ final Uri docUri = buildTreeDocumentUri(
+ TestDocumentsProvider.AUTHORITY, PARENT_DOCUMENT_ID, DOCUMENT_ID);
+ assertNull(DocumentsContract.findPath(mResolver, docUri));
+ }
+
+ public void testFindPath_treeUri_throwsOnNonNullRootId() throws Exception {
+ mProvider.nextIsChildDocument = true;
+
+ mProvider.nextPath = new Path(ROOT_ID, Arrays.asList(PARENT_DOCUMENT_ID, DOCUMENT_ID));
+
+ final Uri docUri = buildTreeDocumentUri(
+ TestDocumentsProvider.AUTHORITY, PARENT_DOCUMENT_ID, DOCUMENT_ID);
+ assertNull(DocumentsContract.findPath(mResolver, docUri));
+ }
+
+ public void testFindPath_treeUri_throwsOnDifferentParentDocId() throws Exception {
+ mProvider.nextIsChildDocument = true;
+
+ mProvider.nextPath = new Path(
+ null, Arrays.asList(ANCESTOR_DOCUMENT_ID, PARENT_DOCUMENT_ID, DOCUMENT_ID));
+
+ final Uri docUri = buildTreeDocumentUri(
+ TestDocumentsProvider.AUTHORITY, PARENT_DOCUMENT_ID, DOCUMENT_ID);
+ assertNull(DocumentsContract.findPath(mResolver, docUri));
+ }
+
+ private static Uri buildTreeDocumentUri(String authority, String parentDocId, String docId) {
+ final Uri treeUri = DocumentsContract.buildTreeDocumentUri(authority, parentDocId);
+ return DocumentsContract.buildDocumentUriUsingTree(treeUri, docId);
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/TestDocumentsProvider.java b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
new file mode 100644
index 0000000..8dcf566
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/TestDocumentsProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.provider;
+
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Path;
+
+import org.mockito.Mockito;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Provides a test double of {@link DocumentsProvider}.
+ */
+public class TestDocumentsProvider extends DocumentsProvider {
+ public static final String AUTHORITY = "android.provider.TestDocumentsProvider";
+
+ public Path nextPath;
+
+ public boolean nextIsChildDocument;
+
+ public String lastDocumentId;
+ public String lastParentDocumentId;
+
+ @Override
+ public void attachInfoForTesting(Context context, ProviderInfo info) {
+ context = new TestContext(context);
+ super.attachInfoForTesting(context, info);
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(String documentId, String mode,
+ CancellationSignal signal) throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ return nextIsChildDocument;
+ }
+
+ @Override
+ public Path findPath(String documentId, @Nullable String parentDocumentId) {
+ lastDocumentId = documentId;
+ lastParentDocumentId = parentDocumentId;
+
+ return nextPath;
+ }
+
+ @Override
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ @Override
+ protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ private static class TestContext extends ContextWrapper {
+
+ private TestContext(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, String message) {
+ // Always granted
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.APP_OPS_SERVICE.equals(name)) {
+ return Mockito.mock(AppOpsManager.class);
+ }
+
+ return super.getSystemService(name);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/coretests/src/android/util/LocalLogTest.java
new file mode 100644
index 0000000..a63c8a0
--- /dev/null
+++ b/core/tests/coretests/src/android/util/LocalLogTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+public class LocalLogTest extends TestCase {
+
+ public void testA() {
+ String[] lines = {
+ "foo",
+ "bar",
+ "baz"
+ };
+ String[] want = lines;
+ testcase(new LocalLog(10), lines, want);
+ }
+
+ public void testB() {
+ String[] lines = {
+ "foo",
+ "bar",
+ "baz"
+ };
+ String[] want = {};
+ testcase(new LocalLog(0), lines, want);
+ }
+
+ public void testC() {
+ String[] lines = {
+ "dropped",
+ "dropped",
+ "dropped",
+ "dropped",
+ "dropped",
+ "dropped",
+ "foo",
+ "bar",
+ "baz",
+ };
+ String[] want = {
+ "foo",
+ "bar",
+ "baz",
+ };
+ testcase(new LocalLog(3), lines, want);
+ }
+
+ void testcase(LocalLog logger, String[] input, String[] want) {
+ for (String l : input) {
+ logger.log(l);
+ }
+ verifyAllLines(want, dump(logger).split("\n"));
+ verifyAllLines(reverse(want), reverseDump(logger).split("\n"));
+ }
+
+ void verifyAllLines(String[] wantLines, String[] gotLines) {
+ for (int i = 0; i < wantLines.length; i++) {
+ String want = wantLines[i];
+ String got = gotLines[i];
+ String msg = String.format("%s did not contain %s", quote(got), quote(want));
+ assertTrue(msg, got.contains(want));
+ }
+ }
+
+ static String dump(LocalLog logger) {
+ StringWriter buffer = new StringWriter();
+ PrintWriter writer = new PrintWriter(buffer);
+ logger.dump(null, writer, new String[0]);
+ return buffer.toString();
+ }
+
+ static String reverseDump(LocalLog logger) {
+ StringWriter buffer = new StringWriter();
+ PrintWriter writer = new PrintWriter(buffer);
+ logger.reverseDump(null, writer, new String[0]);
+ return buffer.toString();
+ }
+
+ static String quote(String s) {
+ return '"' + s + '"';
+ }
+
+ static String[] reverse(String[] ary) {
+ List<String> ls = Arrays.asList(ary);
+ Collections.reverse(ls);
+ return ls.toArray(new String[ary.length]);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java
deleted file mode 100644
index 2370627..0000000
--- a/core/tests/coretests/src/android/util/TimeUtilsTest.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * 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.util;
-
-import junit.framework.TestCase;
-
-import java.util.Calendar;
-import java.util.TimeZone;
-
-/**
- * TimeUtilsTest tests the time zone guesser.
- */
-public class TimeUtilsTest extends TestCase {
- public void testMainstream() throws Exception {
- String[] mainstream = new String[] {
- "America/New_York", // Eastern
- "America/Chicago", // Central
- "America/Denver", // Mountain
- "America/Los_Angeles", // Pacific
- "America/Anchorage", // Alaska
- "Pacific/Honolulu", // Hawaii, no DST
- };
-
- for (String name : mainstream) {
- TimeZone tz = TimeZone.getTimeZone(name);
- Calendar c = Calendar.getInstance(tz);
- TimeZone guess;
-
- c.set(2008, Calendar.OCTOBER, 20, 12, 00, 00);
- guess = guess(c, "us");
- assertEquals(name, guess.getID());
-
- c.set(2009, Calendar.JANUARY, 20, 12, 00, 00);
- guess = guess(c, "us");
- assertEquals(name, guess.getID());
- }
- }
-
- public void testWeird() throws Exception {
- String[] weird = new String[] {
- "America/Phoenix", // Mountain, no DST
- "America/Adak", // Same as Hawaii, but with DST
- };
-
- for (String name : weird) {
- TimeZone tz = TimeZone.getTimeZone(name);
- Calendar c = Calendar.getInstance(tz);
- TimeZone guess;
-
- c.set(2008, Calendar.OCTOBER, 20, 12, 00, 00);
- guess = guess(c, "us");
- assertEquals(name, guess.getID());
- }
- }
-
- public void testOld() throws Exception {
- String[] old = new String[] {
- "America/Indiana/Indianapolis", // Eastern, formerly no DST
- };
-
- for (String name : old) {
- TimeZone tz = TimeZone.getTimeZone(name);
- Calendar c = Calendar.getInstance(tz);
- TimeZone guess;
-
- c.set(2005, Calendar.OCTOBER, 20, 12, 00, 00);
- guess = guess(c, "us");
- assertEquals(name, guess.getID());
- }
- }
-
- public void testWorld() throws Exception {
- String[] world = new String[] {
- "ad", "Europe/Andorra",
- "ae", "Asia/Dubai",
- "af", "Asia/Kabul",
- "ag", "America/Antigua",
- "ai", "America/Anguilla",
- "al", "Europe/Tirane",
- "am", "Asia/Yerevan",
- "an", "America/Curacao",
- "ao", "Africa/Luanda",
- "aq", "Antarctica/McMurdo",
- "aq", "Antarctica/DumontDUrville",
- "aq", "Antarctica/Casey",
- "aq", "Antarctica/Davis",
- "aq", "Antarctica/Mawson",
- "aq", "Antarctica/Syowa",
- "aq", "Antarctica/Rothera",
- "aq", "Antarctica/Palmer",
- "ar", "America/Argentina/Buenos_Aires",
- "as", "Pacific/Pago_Pago",
- "at", "Europe/Vienna",
- "au", "Australia/Sydney",
- "au", "Australia/Adelaide",
- "au", "Australia/Perth",
- "au", "Australia/Eucla",
- "aw", "America/Aruba",
- "ax", "Europe/Mariehamn",
- "az", "Asia/Baku",
- "ba", "Europe/Sarajevo",
- "bb", "America/Barbados",
- "bd", "Asia/Dhaka",
- "be", "Europe/Brussels",
- "bf", "Africa/Ouagadougou",
- "bg", "Europe/Sofia",
- "bh", "Asia/Bahrain",
- "bi", "Africa/Bujumbura",
- "bj", "Africa/Porto-Novo",
- "bm", "Atlantic/Bermuda",
- "bn", "Asia/Brunei",
- "bo", "America/La_Paz",
- "br", "America/Noronha",
- "br", "America/Sao_Paulo",
- "br", "America/Manaus",
- "bs", "America/Nassau",
- "bt", "Asia/Thimphu",
- "bw", "Africa/Gaborone",
- "by", "Europe/Minsk",
- "bz", "America/Belize",
- "ca", "America/St_Johns",
- "ca", "America/Halifax",
- "ca", "America/Toronto",
- "ca", "America/Winnipeg",
- "ca", "America/Edmonton",
- "ca", "America/Vancouver",
- "cc", "Indian/Cocos",
- "cd", "Africa/Lubumbashi",
- "cd", "Africa/Kinshasa",
- "cf", "Africa/Bangui",
- "cg", "Africa/Brazzaville",
- "ch", "Europe/Zurich",
- "ci", "Africa/Abidjan",
- "ck", "Pacific/Rarotonga",
- "cl", "America/Santiago",
- "cl", "Pacific/Easter",
- "cm", "Africa/Douala",
- "cn", "Asia/Shanghai",
- "co", "America/Bogota",
- "cr", "America/Costa_Rica",
- "cu", "America/Havana",
- "cv", "Atlantic/Cape_Verde",
- "cx", "Indian/Christmas",
- "cy", "Asia/Nicosia",
- "cz", "Europe/Prague",
- "de", "Europe/Berlin",
- "dj", "Africa/Djibouti",
- "dk", "Europe/Copenhagen",
- "dm", "America/Dominica",
- "do", "America/Santo_Domingo",
- "dz", "Africa/Algiers",
- "ec", "America/Guayaquil",
- "ec", "Pacific/Galapagos",
- "ee", "Europe/Tallinn",
- "eg", "Africa/Cairo",
- "eh", "Africa/El_Aaiun",
- "er", "Africa/Asmara",
- "es", "Europe/Madrid",
- "es", "Atlantic/Canary",
- "et", "Africa/Addis_Ababa",
- "fi", "Europe/Helsinki",
- "fj", "Pacific/Fiji",
- "fk", "Atlantic/Stanley",
- "fm", "Pacific/Ponape",
- "fm", "Pacific/Truk",
- "fo", "Atlantic/Faroe",
- "fr", "Europe/Paris",
- "ga", "Africa/Libreville",
- "gb", "Europe/London",
- "gd", "America/Grenada",
- "ge", "Asia/Tbilisi",
- "gf", "America/Cayenne",
- "gg", "Europe/Guernsey",
- "gh", "Africa/Accra",
- "gi", "Europe/Gibraltar",
- "gl", "America/Danmarkshavn",
- "gl", "America/Scoresbysund",
- "gl", "America/Godthab",
- "gl", "America/Thule",
- "gm", "Africa/Banjul",
- "gn", "Africa/Conakry",
- "gp", "America/Guadeloupe",
- "gq", "Africa/Malabo",
- "gr", "Europe/Athens",
- "gs", "Atlantic/South_Georgia",
- "gt", "America/Guatemala",
- "gu", "Pacific/Guam",
- "gw", "Africa/Bissau",
- "gy", "America/Guyana",
- "hk", "Asia/Hong_Kong",
- "hn", "America/Tegucigalpa",
- "hr", "Europe/Zagreb",
- "ht", "America/Port-au-Prince",
- "hu", "Europe/Budapest",
- "id", "Asia/Jayapura",
- "id", "Asia/Makassar",
- "id", "Asia/Jakarta",
- "ie", "Europe/Dublin",
- "il", "Asia/Jerusalem",
- "im", "Europe/Isle_of_Man",
- "in", "Asia/Calcutta",
- "io", "Indian/Chagos",
- "iq", "Asia/Baghdad",
- "ir", "Asia/Tehran",
- "is", "Atlantic/Reykjavik",
- "it", "Europe/Rome",
- "je", "Europe/Jersey",
- "jm", "America/Jamaica",
- "jo", "Asia/Amman",
- "jp", "Asia/Tokyo",
- "ke", "Africa/Nairobi",
- "kg", "Asia/Bishkek",
- "kh", "Asia/Phnom_Penh",
- "ki", "Pacific/Kiritimati",
- "ki", "Pacific/Enderbury",
- "ki", "Pacific/Tarawa",
- "km", "Indian/Comoro",
- "kn", "America/St_Kitts",
- "kp", "Asia/Pyongyang",
- "kr", "Asia/Seoul",
- "kw", "Asia/Kuwait",
- "ky", "America/Cayman",
- "kz", "Asia/Almaty",
- "kz", "Asia/Aqtau",
- "la", "Asia/Vientiane",
- "lb", "Asia/Beirut",
- "lc", "America/St_Lucia",
- "li", "Europe/Vaduz",
- "lk", "Asia/Colombo",
- "lr", "Africa/Monrovia",
- "ls", "Africa/Maseru",
- "lt", "Europe/Vilnius",
- "lu", "Europe/Luxembourg",
- "lv", "Europe/Riga",
- "ly", "Africa/Tripoli",
- "ma", "Africa/Casablanca",
- "mc", "Europe/Monaco",
- "md", "Europe/Chisinau",
- "me", "Europe/Podgorica",
- "mg", "Indian/Antananarivo",
- "mh", "Pacific/Majuro",
- "mk", "Europe/Skopje",
- "ml", "Africa/Bamako",
- "mm", "Asia/Rangoon",
- "mn", "Asia/Choibalsan",
- "mn", "Asia/Hovd",
- "mo", "Asia/Macau",
- "mp", "Pacific/Saipan",
- "mq", "America/Martinique",
- "mr", "Africa/Nouakchott",
- "ms", "America/Montserrat",
- "mt", "Europe/Malta",
- "mu", "Indian/Mauritius",
- "mv", "Indian/Maldives",
- "mw", "Africa/Blantyre",
- "mx", "America/Mexico_City",
- "mx", "America/Chihuahua",
- "mx", "America/Tijuana",
- "my", "Asia/Kuala_Lumpur",
- "mz", "Africa/Maputo",
- "na", "Africa/Windhoek",
- "nc", "Pacific/Noumea",
- "ne", "Africa/Niamey",
- "nf", "Pacific/Norfolk",
- "ng", "Africa/Lagos",
- "ni", "America/Managua",
- "nl", "Europe/Amsterdam",
- "no", "Europe/Oslo",
- "np", "Asia/Katmandu",
- "nr", "Pacific/Nauru",
- "nu", "Pacific/Niue",
- "nz", "Pacific/Auckland",
- "nz", "Pacific/Chatham",
- "om", "Asia/Muscat",
- "pa", "America/Panama",
- "pe", "America/Lima",
- "pf", "Pacific/Gambier",
- "pf", "Pacific/Marquesas",
- "pf", "Pacific/Tahiti",
- "pg", "Pacific/Port_Moresby",
- "ph", "Asia/Manila",
- "pk", "Asia/Karachi",
- "pl", "Europe/Warsaw",
- "pm", "America/Miquelon",
- "pn", "Pacific/Pitcairn",
- "pr", "America/Puerto_Rico",
- "ps", "Asia/Gaza",
- "pt", "Europe/Lisbon",
- "pt", "Atlantic/Azores",
- "pw", "Pacific/Palau",
- "py", "America/Asuncion",
- "qa", "Asia/Qatar",
- "re", "Indian/Reunion",
- "ro", "Europe/Bucharest",
- "rs", "Europe/Belgrade",
- "ru", "Asia/Kamchatka",
- "ru", "Asia/Magadan",
- "ru", "Asia/Vladivostok",
- "ru", "Asia/Yakutsk",
- "ru", "Asia/Irkutsk",
- "ru", "Asia/Krasnoyarsk",
- "ru", "Asia/Novosibirsk",
- "ru", "Asia/Yekaterinburg",
- "ru", "Europe/Samara",
- "ru", "Europe/Moscow",
- "ru", "Europe/Kaliningrad",
- "rw", "Africa/Kigali",
- "sa", "Asia/Riyadh",
- "sb", "Pacific/Guadalcanal",
- "sc", "Indian/Mahe",
- "sd", "Africa/Khartoum",
- "se", "Europe/Stockholm",
- "sg", "Asia/Singapore",
- "sh", "Atlantic/St_Helena",
- "si", "Europe/Ljubljana",
- "sj", "Arctic/Longyearbyen",
- "sk", "Europe/Bratislava",
- "sl", "Africa/Freetown",
- "sm", "Europe/San_Marino",
- "sn", "Africa/Dakar",
- "so", "Africa/Mogadishu",
- "sr", "America/Paramaribo",
- "st", "Africa/Sao_Tome",
- "sv", "America/El_Salvador",
- "sy", "Asia/Damascus",
- "sz", "Africa/Mbabane",
- "tc", "America/Grand_Turk",
- "td", "Africa/Ndjamena",
- "tf", "Indian/Kerguelen",
- "tg", "Africa/Lome",
- "th", "Asia/Bangkok",
- "tj", "Asia/Dushanbe",
- "tk", "Pacific/Fakaofo",
- "tl", "Asia/Dili",
- "tm", "Asia/Ashgabat",
- "tn", "Africa/Tunis",
- "to", "Pacific/Tongatapu",
- "tr", "Europe/Istanbul",
- "tt", "America/Port_of_Spain",
- "tv", "Pacific/Funafuti",
- "tw", "Asia/Taipei",
- "tz", "Africa/Dar_es_Salaam",
- "ua", "Europe/Kiev",
- "ug", "Africa/Kampala",
- "um", "Pacific/Wake",
- "um", "Pacific/Johnston",
- "um", "Pacific/Midway",
- "us", "America/New_York",
- "us", "America/Chicago",
- "us", "America/Denver",
- "us", "America/Los_Angeles",
- "us", "America/Anchorage",
- "us", "Pacific/Honolulu",
- "uy", "America/Montevideo",
- "uz", "Asia/Tashkent",
- "va", "Europe/Vatican",
- "vc", "America/St_Vincent",
- "ve", "America/Caracas",
- "vg", "America/Tortola",
- "vi", "America/St_Thomas",
- "vn", "Asia/Saigon",
- "vu", "Pacific/Efate",
- "wf", "Pacific/Wallis",
- "ws", "Pacific/Apia",
- "ye", "Asia/Aden",
- "yt", "Indian/Mayotte",
- "za", "Africa/Johannesburg",
- "zm", "Africa/Lusaka",
- "zw", "Africa/Harare",
- };
-
- for (int i = 0; i < world.length; i += 2) {
- String country = world[i];
- String name = world[i + 1];
-
- TimeZone tz = TimeZone.getTimeZone(name);
- Calendar c = Calendar.getInstance(tz);
- TimeZone guess;
-
- c.set(2009, Calendar.JULY, 20, 12, 00, 00);
- guess = guess(c, country);
- assertEquals(name, guess.getID());
-
- c.set(2009, Calendar.JANUARY, 20, 12, 00, 00);
- guess = guess(c, country);
- assertEquals(name, guess.getID());
- }
- }
-
- public void testWorldWeird() throws Exception {
- String[] world = new String[] {
- // Distinguisable from Sydney only when DST not in effect
- "au", "Australia/Lord_Howe",
- };
-
- for (int i = 0; i < world.length; i += 2) {
- String country = world[i];
- String name = world[i + 1];
-
- TimeZone tz = TimeZone.getTimeZone(name);
- Calendar c = Calendar.getInstance(tz);
- TimeZone guess;
-
- c.set(2009, Calendar.JULY, 20, 12, 00, 00);
- guess = guess(c, country);
- assertEquals(name, guess.getID());
- }
- }
-
- private static TimeZone guess(Calendar c, String country) {
- return TimeUtils.getTimeZone(c.get(c.ZONE_OFFSET) + c.get(c.DST_OFFSET),
- c.get(c.DST_OFFSET) != 0,
- c.getTimeInMillis(),
- country);
- }
-
- public void testFormatDuration() {
- assertFormatDuration("0", 0);
- assertFormatDuration("-1ms", -1);
- assertFormatDuration("+1ms", 1);
- assertFormatDuration("+10ms", 10);
- assertFormatDuration("+100ms", 100);
- assertFormatDuration("+101ms", 101);
- assertFormatDuration("+330ms", 330);
- assertFormatDuration("+1s0ms", 1000);
- assertFormatDuration("+1s330ms", 1330);
- assertFormatDuration("+10s24ms", 10024);
- assertFormatDuration("+1m0s30ms", 60030);
- assertFormatDuration("+1h0m0s30ms", 3600030);
- assertFormatDuration("+1d0h0m0s30ms", 86400030);
- }
-
- public void testFormatHugeDuration() {
- assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
- assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L);
- }
-
- private void assertFormatDuration(String expected, long duration) {
- StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(duration, sb);
- assertEquals("formatDuration(" + duration + ")", expected, sb.toString());
- }
-}
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..71dd526 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -17,7 +17,6 @@
package android.widget;
import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
-import static android.support.test.espresso.action.ViewActions.longClick;
import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
@@ -37,6 +36,7 @@
import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.action.ViewActions.pressKey;
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
@@ -44,24 +44,25 @@
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
-import com.android.frameworks.coretests.R;
import android.support.test.espresso.action.EspressoKey;
import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
import android.text.Selection;
import android.text.Spannable;
import android.text.InputType;
import android.view.KeyEvent;
-import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.is;
+import com.android.frameworks.coretests.R;
/**
* Tests the TextView widget from an Activity
*/
+@MediumTest
public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
public TextViewActivityTest() {
@@ -74,7 +75,6 @@
getActivity();
}
- @SmallTest
public void testTypedTextIsOnScreen() throws Exception {
final String helloWorld = "Hello world!";
onView(withId(R.id.textview)).perform(click());
@@ -83,7 +83,6 @@
onView(withId(R.id.textview)).check(matches(withText(helloWorld)));
}
- @SmallTest
public void testPositionCursorAtTextAtIndex() throws Exception {
final String helloWorld = "Hello world!";
onView(withId(R.id.textview)).perform(click());
@@ -95,7 +94,6 @@
onView(withId(R.id.textview)).check(matches(withText("Hello orld!")));
}
- @SmallTest
public void testPositionCursorAtTextAtIndex_arabic() throws Exception {
// Arabic text. The expected cursorable boundary is
// | \u0623 \u064F | \u067A | \u0633 \u0652 |
@@ -117,7 +115,6 @@
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
}
- @SmallTest
public void testPositionCursorAtTextAtIndex_devanagari() throws Exception {
// Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
final String text = "\u0915\u093E";
@@ -132,7 +129,6 @@
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
}
- @SmallTest
public void testLongPressToSelect() throws Exception {
final String helloWorld = "Hello Kirk!";
onView(withId(R.id.textview)).perform(click());
@@ -143,7 +139,6 @@
onView(withId(R.id.textview)).check(hasSelection("Kirk"));
}
- @SmallTest
public void testLongPressEmptySpace() throws Exception {
final String helloWorld = "Hello big round sun!";
onView(withId(R.id.textview)).perform(click());
@@ -158,7 +153,6 @@
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(helloWorld.length()));
}
- @SmallTest
public void testLongPressAndDragToSelect() throws Exception {
final String helloWorld = "Hello little handsome boy!";
onView(withId(R.id.textview)).perform(click());
@@ -169,7 +163,20 @@
onView(withId(R.id.textview)).check(hasSelection("little handsome"));
}
- @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"));
+ }
+
public void testDragAndDrop() throws Exception {
final String text = "abc def ghi.";
onView(withId(R.id.textview)).perform(click());
@@ -191,7 +198,6 @@
onView(withId(R.id.textview)).check(matches(withText(text)));
}
- @SmallTest
public void testDoubleTapToSelect() throws Exception {
final String helloWorld = "Hello SuetYi!";
onView(withId(R.id.textview)).perform(click());
@@ -202,7 +208,6 @@
onView(withId(R.id.textview)).check(hasSelection("SuetYi"));
}
- @SmallTest
public void testDoubleTapAndDragToSelect() throws Exception {
final String helloWorld = "Hello young beautiful girl!";
onView(withId(R.id.textview)).perform(click());
@@ -213,7 +218,6 @@
onView(withId(R.id.textview)).check(hasSelection("young beautiful"));
}
- @SmallTest
public void testDoubleTapAndDragToSelect_multiLine() throws Exception {
final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop";
onView(withId(R.id.textview)).perform(click());
@@ -223,7 +227,6 @@
onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm"));
}
- @SmallTest
public void testSelectBackwordsByTouch() throws Exception {
final String helloWorld = "Hello king of the Jungle!";
onView(withId(R.id.textview)).perform(click());
@@ -234,7 +237,6 @@
onView(withId(R.id.textview)).check(hasSelection("king of the"));
}
- @SmallTest
public void testToolbarAppearsAfterSelection() throws Exception {
final String text = "Toolbar appears after selection.";
onView(withId(R.id.textview)).perform(click());
@@ -251,7 +253,6 @@
assertFloatingToolbarIsNotDisplayed();
}
- @SmallTest
public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint()
throws Exception {
// after the hint layout change, the floating toolbar was not visible in the case below
@@ -279,7 +280,6 @@
assertFloatingToolbarIsDisplayed();
}
- @SmallTest
public void testToolbarAndInsertionHandle() throws Exception {
final String text = "text";
onView(withId(R.id.textview)).perform(click());
@@ -299,7 +299,6 @@
getActivity().getString(com.android.internal.R.string.cut));
}
- @SmallTest
public void testToolbarAndSelectionHandle() throws Exception {
final String text = "abcd efg hijk";
onView(withId(R.id.textview)).perform(click());
@@ -335,7 +334,6 @@
getActivity().getString(com.android.internal.R.string.cut));
}
- @SmallTest
public void testInsertionHandle() throws Exception {
final String text = "abcd efg hijk ";
onView(withId(R.id.textview)).perform(click());
@@ -355,7 +353,6 @@
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
}
- @SmallTest
public void testInsertionHandle_multiLine() throws Exception {
final String text = "abcd\n" + "efg\n" + "hijk\n";
onView(withId(R.id.textview)).perform(click());
@@ -375,7 +372,6 @@
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
}
- @SmallTest
public void testSelectionHandles() throws Exception {
final String text = "abcd efg hijk lmn";
onView(withId(R.id.textview)).perform(click());
@@ -400,7 +396,6 @@
onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
}
- @SmallTest
public void testSelectionHandles_bidi() throws Exception {
final String text = "abc \u0621\u0622\u0623 def";
onView(withId(R.id.textview)).perform(click());
@@ -445,7 +440,6 @@
onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623 def"));
}
- @SmallTest
public void testSelectionHandles_multiLine() throws Exception {
final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
onView(withId(R.id.textview)).perform(click());
@@ -470,7 +464,6 @@
onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
}
- @SmallTest
public void testSelectionHandles_multiLine_rtl() throws Exception {
// Arabic text.
final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
@@ -503,7 +496,6 @@
}
- @SmallTest
public void testSelectionHandles_doesNotPassAnotherHandle() throws Exception {
final String text = "abcd efg hijk lmn";
onView(withId(R.id.textview)).perform(click());
@@ -521,7 +513,6 @@
onView(withId(R.id.textview)).check(hasSelection("e"));
}
- @SmallTest
public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() throws Exception {
final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
onView(withId(R.id.textview)).perform(click());
@@ -539,7 +530,6 @@
onView(withId(R.id.textview)).check(hasSelection("h"));
}
- @SmallTest
public void testSelectionHandles_snapToWordBoundary() throws Exception {
final String text = "abcd efg hijk lmn opqr";
onView(withId(R.id.textview)).perform(click());
@@ -592,7 +582,6 @@
onView(withId(R.id.textview)).check(hasSelection("hijk lmn opq"));
}
- @SmallTest
public void testSelectionHandles_snapToWordBoundary_multiLine() throws Exception {
final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu";
onView(withId(R.id.textview)).perform(click());
@@ -628,7 +617,6 @@
onView(withId(R.id.textview)).check(hasSelection("hijk"));
}
- @SmallTest
public void testSetSelectionAndActionMode() throws Exception {
final String text = "abc def";
onView(withId(R.id.textview)).perform(click());
@@ -692,7 +680,6 @@
getActivity().getString(com.android.internal.R.string.copy));
}
- @SmallTest
public void testTransientState() throws Exception {
final String text = "abc def";
onView(withId(R.id.textview)).perform(click());
diff --git a/core/tests/coretests/src/com/android/internal/os/DebugTest.java b/core/tests/coretests/src/com/android/internal/os/DebugTest.java
index 88c7d1b..efb78d7 100644
--- a/core/tests/coretests/src/com/android/internal/os/DebugTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/DebugTest.java
@@ -64,4 +64,12 @@
public void testGetCallers() {
assertTrue(callDepth1().matches(EXPECTED_GET_CALLERS));
}
+
+ /**
+ * Regression test for b/31943543. Note: must be run under CheckJNI to detect the issue.
+ */
+ public void testGetMemoryInfo() {
+ Debug.MemoryInfo info = new Debug.MemoryInfo();
+ Debug.getMemoryInfo(-1, info);
+ }
}
diff --git a/core/tests/systemproperties/Android.mk b/core/tests/systemproperties/Android.mk
index ffc1282..e16c367 100644
--- a/core/tests/systemproperties/Android.mk
+++ b/core/tests/systemproperties/Android.mk
@@ -11,7 +11,6 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreSystemPropertiesTests
-LOCAL_JAVA_LANGUAGE_VERSION := 1.8
LOCAL_CERTIFICATE := platform
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index 8ddb982..7daf85c 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -289,6 +289,8 @@
to: /design/patterns/app-structure.html
- from: /guide/practices/ui_guidelines/menu_design.html
to: /design/patterns/actionbar.html
+- from: /design/patterns/accessibility.html
+ to: https://material.google.com/usability/accessibility.html
- from: /design/get-started/ui-overview.html
to: /design/handhelds/index.html
- from: /design/building-blocks/buttons.html
@@ -430,7 +432,13 @@
- from: /training/cloudsync/aesync.html
to: /google/gcm/index.html
- from: /training/cloudsync/index.html
- to: /training/backup/index.html
+ to: /guide/topics/data/backup.html
+- from: /training/backup/index.html
+ to: /guide/topics/data/backup.html
+- from: /training/backup/autosyncapi.html
+ to: /guide/topics/data/autobackup.html
+- from: /training/backup/backupapi.html
+ to: /guide/topics/data/keyvaluebackup.html
- from: /training/basics/location/...
to: /training/location/...
- from: /training/monetization/index.html
@@ -796,11 +804,11 @@
- from: /preview/features/app-linking.html
to: /training/app-links/index.html
- from: /preview/backup/index.html
- to: /training/backup/autosyncapi.html
+ to: /guide/topics/data/backup/autobackup.html
- from: /preview/features/power-mgmt.html
to: /training/monitoring-device-state/doze-standby.html
- from: /preview/dev-community
- to: https://plus.google.com/communities/103655397235276743411
+ to: https://plus.google.com/communities/105153134372062985968
- from: /preview/bug
to: https://source.android.com/source/report-bugs.html
- from: /preview/bug/...
@@ -842,6 +850,10 @@
to: /topic/performance/power/network/gather-data.html
- from: /training/performance/battery/network/index.html
to: /topic/performance/power/network/index.html
+- from: /training/articles/memory.html
+ to: /topic/performance/memory.html
+- from: /topic/performance/optimizing-view-hierarchies.html
+ to: /topic/performance/rendering/optimizing-view-hierarchies.html
# Redirects for the new [dac]/topic/libraries/ area
@@ -1225,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
@@ -1242,6 +1254,46 @@
to: /studio/write/lint.html?utm_source=android-studio
- from: /r/studio-ui/gradle-console.html
to: /studio/run/index.html?utm_source=android-studio#gradle-console
+- from: /r/studio-ui/app-indexing-test.html
+ to: /studio/write/app-link-indexing.html#appindexingtest?utm_source=android-studio
+- from: /r/studio-ui/vcs.html
+ to: /studio/intro/index.html#version_control_basics?utm_source=android-studio
+- from: /r/studio-ui/create-new-module.html
+ to: /studio/projects/index.html#ApplicationModules?utm_source=android-studio
+- from: /r/studio-ui/build-variants.html
+ to: /studio/run/index.html#changing-variant?utm_source=android-studio
+- from: /r/studio-ui/generate-signed-apk.html
+ to: /studio/publish/app-signing.html#release-mode?utm_source=android-studio
+- from: /r/studio-ui/import-project-vcs.html
+ to: /studio/projects/create-project.html#ImportAProject?utm_source=android-studio
+- from: /r/studio-ui/apk-analyzer.html
+ to: /studio/build/apk-analyzer.html?utm_source=android-studio
+- from: /r/studio-ui/breakpoints.html
+ to: /studio/debug/index.html#breakPointsView?utm_source=android-studio
+- from: /r/studio-ui/attach-debugger-to-process.html
+ to: /studio/debug/index.html?utm_source=android-studio
+- from: /r/studio-ui/import-sample.html
+ to: /samples/index.html?utm_source=android-studio
+- from: /r/studio-ui/import-module.html
+ to: /studio/projects/add-app-module.html#ImportAModule?utm_source=android-studio
+- from: /r/studio-ui/import-project.html
+ to: /studio/projects/create-project.html#ImportAProject?utm_source=android-studio
+- from: /r/studio-ui/create-project.html
+ to: /studio/projects/create-project.html?utm_source=android-studio
+- from: /r/studio-ui/new-activity.html
+ to: /studio/projects/template.html?utm_source=android-studio
+- from: /r/studio-ui/new-resource-file.html
+ to: /studio/write/add-resources.html?utm_source=android-studio
+- from: /r/studio-ui/new-resource-dir.html
+ to: /studio/write/add-resources.html#add_a_resource_directory?utm_source=android-studio
+- from: /r/studio-ui/configure-component.html
+ to: /studio/write/add-resources.html?utm_source=android-studio
+- from: /r/studio-ui/ninepatch.html
+ to: /studio/write/draw9patch.html?utm_source=android-studio
+- from: /r/studio-ui/firebase-assistant.html
+ to: /studio/write/firebase.html?utm_source=android-studio
+- from: /r/studio-ui/ir-flight-recorder.html
+ to: /studio/run/index.html?utm_source=android-studio#submit-feedback
# Redirects from (removed) N Preview documentation
- from: /preview/features/afw.html
diff --git a/docs/html/about/versions/_project.yaml b/docs/html/about/versions/_project.yaml
new file mode 100644
index 0000000..3f0e85e
--- /dev/null
+++ b/docs/html/about/versions/_project.yaml
@@ -0,0 +1,6 @@
+name: "Versions"
+home_url: /about/versions/
+description: "Android, the world's most popular mobile platform"
+content_license: cc3-apache2
+buganizer_id: 30209417
+parent_project_metadata_path: /about/_project.yaml
diff --git a/docs/html/about/versions/nougat/android-7.0-samples.jd b/docs/html/about/versions/nougat/android-7.0-samples.jd
index e283a7a..ff63bef 100644
--- a/docs/html/about/versions/nougat/android-7.0-samples.jd
+++ b/docs/html/about/versions/nougat/android-7.0-samples.jd
@@ -6,7 +6,7 @@
<p>
Use the code samples below to learn about Android 7.0 capabilities and APIs. To
- download the samples in Android Studio, select the <b>File > Import
+ download the samples in Android Studio, select the <b>File > New > Import
Samples</b> menu option.
</p>
diff --git a/docs/html/auto/_project.yaml b/docs/html/auto/_project.yaml
new file mode 100644
index 0000000..fc4ab2b
--- /dev/null
+++ b/docs/html/auto/_project.yaml
@@ -0,0 +1,6 @@
+name: "Auto"
+home_url: /auto/
+description: "Let drivers listen to and control content in your music and other audio apps."
+content_license: cc3-apache2
+buganizer_id: 30209417
+parent_project_metadata_path: /about/_project.yaml
diff --git a/docs/html/design/_book.yaml b/docs/html/design/_book.yaml
index 18b4719..8ffa9a4 100644
--- a/docs/html/design/_book.yaml
+++ b/docs/html/design/_book.yaml
@@ -117,8 +117,6 @@
path: /design/patterns/pure-android.html
- title: Compatibility
path: /design/patterns/compatibility.html
- - title: Accessibility
- path: /design/patterns/accessibility.html
- title: Help
path: /design/patterns/help.html
diff --git a/docs/html/design/patterns/accessibility.jd b/docs/html/design/patterns/accessibility.jd
deleted file mode 100644
index b910294..0000000
--- a/docs/html/design/patterns/accessibility.jd
+++ /dev/null
@@ -1,98 +0,0 @@
-page.title=Accessibility
-page.tags="accessibility","navigation","input"
-page.metaDescription=Design an app that's universally accessible to people with visual impairment, color deficiency, hearing loss, and limited dexterity.
-@jd:body
-
-<a class="notice-designers-material"
- href="http://www.google.com/design/spec/usability/accessibility.html">
- <div>
- <h3>Material Design</h3>
- <p>Accessibility<p>
- </div>
-</a>
-
-<a class="notice-developers" href="{@docRoot}training/accessibility/index.html">
- <div>
- <h3>Developer Docs</h3>
- <p>Implementing Accessibility</p>
- </div>
-</a>
-
-<p>One of Android's missions is to organize the world's information and make it universally accessible and useful. Accessibility is the measure of how successfully a product can be used by people with varying abilities. Our mission applies to all users-including people with disabilities such as visual impairment, color deficiency, hearing loss, and limited dexterity.</p>
-<p><a href="https://www.google.com/#hl=en&q=universal+design&fp=1">Universal design</a> is the practice of making products that are inherently accessible to all users, regardless of ability. The Android design patterns were created in accordance with universal design principles, and following them will help your app meet basic usability standards. Adhering to universal design and enabling Android's accessibility tools will make your app as accessible as possible.</p>
-<p>Robust support for accessibility will increase your app's user base. It may also be required for adoption by some organizations.</p>
-<p><a href="http://www.google.com/accessibility/">Learn more about Google and accessibility.</a></p>
-
-<h2 id="tools">Android's Accessibility Tools</h2>
-<p>Android includes several features that support access for users with visual impairments; they don't require drastic visual changes to your app.</p>
-
-<ul>
- <li><strong><a href="https://play.google.com/store/apps/details?id=com.google.android.marvin.talkback">TalkBack</a></strong> is a pre-installed screen reader service provided by Google. It uses spoken feedback to describe the results of actions such as launching an app, and events such as notifications.</li>
- <li><strong>Explore by Touch</strong> is a system feature that works with TalkBack, allowing you to touch your device's screen and hear what's under your finger via spoken feedback. This feature is helpful to users with low vision.</li>
- <li><strong>Accessibility settings</strong> let you modify your device's display and sound options, such as increasing the text size, changing the speed at which text is spoken, and more.</li>
-</ul>
-
-<p>Some users use hardware or software directional controllers (such as a D-pad, trackball, keyboard) to jump from selection to selection on a screen. They interact with the structure of your app in a linear fashion, similar to 4-way remote control navigation on a television.</p>
-
-<h2 id="tools">Guidelines</h2>
-<p>The Android design principle "I should always know where I am" is key for accessibility concerns. As a user navigates through an application, they need feedback and a mental model of where they are. All users benefit from a strong sense of information hierarchy and an architecture that makes sense. Most users benefit from visual and haptic feedback during their navigation (such as labels, colors, icons, touch feedback) Low vision users benefit from explicit verbal descriptions and large visuals with high contrast.</p>
-<p>As you design your app, think about the labels and notations needed to navigate your app by sound. When using Explore by Touch, the user enables an invisible but audible layer of structure in your application. Like any other aspect of app design, this structure can be simple, elegant, and robust. The following are Android's recommended guidelines to enable effective navigation for all users.</p>
-
-<h4>Make navigation intuitive</h4>
-<p>Design well-defined, clear task flows with minimal navigation steps, especially for major user tasks. Make sure those tasks are navigable via focus controls. </p>
-
-<h4>Use recommended touch target sizes</h4>
-<p>48 dp is the recommended touch target size for on screen elements. Read about <a href="{@docRoot}design/style/metrics-grids.html">Android Metrics and Grids</a> to learn about implementation strategies to help most of your users. For certain users, it may be appropriate to use larger touch targets. An example of this is educational apps, where buttons larger than the minimum recommendations are appropriate for children with developing motor skills and people with manual dexterity challenges.</p>
-
-
-<h4>Label visual UI elements meaningfully</h4>
-<p>In your wireframes, <a href="{@docRoot}guide/topics/ui/accessibility/apps.html#label-ui">label functional UI components</a> that have no visible text. Those components might be buttons, icons, tabs with icons, and icons with state (like stars). Developers can use the <code><a href="{@docRoot}guide/topics/ui/accessibility/apps.html#label-ui">contentDescription</a></code> attribute to set the label.</p>
-
-<div class ="cols">
- <div class="col-8">
- <img src="{@docRoot}design/media/accessibility_contentdesc.png">
- </div>
- <div class="col-5 with-callouts">
- <ol>
- <li class="value-1">group</li>
- <li class="value-2">all contacts</li>
- <li class="value-3">favorites</li>
- <li class="value-4">search</li>
- <li class="value-5">action overflow button</li>
- <li class="value-6">
- <em>when starred:</em> remove from favorites </br>
- <em>when not starred:</em> add to favorties</li>
- <li class="value-7">action overflow button</li>
- <li class="value-8">text message</li>
- </ol>
- </div>
-</div>
-
-<h4>Provide alternatives to affordances that time out</h4>
-<p>Your app may have icons or controls that disappear after a certain amount of time. For example, five seconds after starting a video, playback controls may fade from the screen.</p>
-
-<p>Due to the way that TalkBack works, those controls are not read out loud unless they are focused on. If they fade out from the screen quickly, your user may not even be aware that they are available. Therefore, make sure that you are not relying on timed out controls for high priority task flows. (This is a good universal design guideline too.) If the controls enable an important function, make sure that the user can turn on the controls again and/or their function is duplicated elsewhere. You can also change the behavior of your app when accessibility services are turned on. Your developer may be able to make sure that timed-out controls won't disappear.</p>
-
-<h4>Use standard framework controls or enable TalkBack for custom controls</h4>
-<p>Standard Android framework controls work automatically with accessibility services and have ContentDescriptions built in by default.</p>
-
-<p>An oft-overlooked system control is font size. Users can turn on a system-wide large font size in Settings; using the default system font size in your application will enable the user's preferences in your app as well. To enable system font size in your app, mark text and their associated containers to be measured in <a href="{@docRoot}guide/practices/screens_support.html#screen-independence">scale pixels</a>.</p>
-
-<p>Also, keep in mind that when users have large fonts enabled or speak a different language than you, their type might be larger than the space you've allotted for it. Read <a href="{@docRoot}design/style/devices-displays.html">Devices and Displays</a> and <a href="http://developer.android.com/guide/practices/screens_support.html">Supporting Multiple Screens</a> for design strategies.</p>
-
-<p>If you use custom controls, Android has the developer tools in place to allow adherence to the above guidelines and provide meaningful descriptions about the UI. Provide adequate notation on your wireframes and direct your developer to the <a href="{@docRoot}guide/topics/ui/accessibility/apps.html#custom-views">Custom Views</a> documentation.</p>
-
-<h4>Try it out yourself</h4>
-<p>Turn on the TalkBack service in <strong>Settings > Accessibility</strong> and navigate your application using directional controls or eyes-free navigation.</p>
-
-
-
-<h2>Checklist</h2>
-<ul>
- <li>Make navigation intuitive</li>
- <li>Use recommended touch target sizes</li>
- <li>Label visual UI elements meaningfully</li>
- <li>Provide alternatives to affordances that time out</li>
- <li>Use standard framework controls or enable TalkBack for custom controls</li>
- <li>Try it out yourself</li>
-</ul>
diff --git a/docs/html/distribute/stories/apps.jd b/docs/html/distribute/stories/apps.jd
index 76e9f5a..47f4f7f 100644
--- a/docs/html/distribute/stories/apps.jd
+++ b/docs/html/distribute/stories/apps.jd
@@ -25,8 +25,7 @@
<h2 class="norule">Articles</h2>
<div class="resource-widget resource-flow-layout col-16"
- data-query="type:distribute+tag:developerstory+tag:apps"
- data-sortOrder="-timestamp"
+ data-query="collection:distribute/stories/apps/docs"
data-cardSizes="6x6"
data-items-per-page="15"
data-initial-results="6"></div>
diff --git a/docs/html/distribute/stories/apps/condenast-shopping.jd b/docs/html/distribute/stories/apps/condenast-shopping.jd
new file mode 100644
index 0000000..37c2b1f
--- /dev/null
+++ b/docs/html/distribute/stories/apps/condenast-shopping.jd
@@ -0,0 +1,76 @@
+page.title=Glamour.de Connects Offline and Online Shopping Experiences with Google Play Billing
+page.metaDescription=Condé Nast improves features on its Glamour app.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/glamour.png
+page.timestamp=null
+
+@jd:body
+
+
+<h3>Background</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/glamour-icon.png" />
+</div>
+
+<p>
+ Glamour is one of the main
+ <a class="external-link"
+ href="https://play.google.com/store/apps/developer?id=Cond%C3%A9%20Nast%20Verlag%20GmbH&hl=en">
+ Condé Nast</a> traditional brands. Every year, Glamour hosts a
+ successful shopping event called
+ <a class="external-link"
+ href="https://play.google.com/store/apps/details?id=de.glamour.android&e=-EnableAppDetailsPageRedesign">
+ <em>GLAMOUR Shopping-Week</em></a> in Germany, Austria, and Switzerland.
+ This event has always been print-focused, as readers received a shopping
+ card with the magazine to redeem discounts in selected shops, both offline
+ and online, for one week.
+</p>
+
+<p>
+ In March 2016, Glamour digitized this experience.
+</p>
+
+<h3>What they did</h3>
+
+<p>
+ To make the most of <em>GLAMOUR Shopping-Week</em>, Condé Nast relaunched the
+ <a class="external-link"
+ href="https://play.google.com/store/apps/details?id=de.condenast.glamourde&e=-EnableAppDetailsPageRedesign">
+ GLAMOUR app</a> with a more appealing design and an improved user experience:
+<ul>
+ <li>The main features updated for the shopping week included a shop finder, online offers, and
+ a digital shopping card.</li>
+ <li>The current e-paper magazine was made available through the app and sold via Google Play
+ Billing.</li>
+ <li>They offered readers the in-app purchase of digital shopping cards and activation codes
+ through Google Play Billing. Readers can activate digital shopping cards via in-app
+ purchases or with the print shopping card activation code.</li>
+ <li>The online and offline shopping experience was also supported by online shopping discount
+ codes in the app or offline through the shop finder.</li>
+</ul>
+</p>
+
+<h3>Results</h3>
+
+<p>
+ The offline and online combination resulted in positive engagement both in terms of app
+ installs and sales:
+<ul>
+ <li>There were 130,000 new app downloads, and 100,000 users enabled location access to use
+ the shop finder.</li>
+ <li><strong>Sessions increased by 140%</strong> compared to previous weeks. Session length
+ doubled and <strong>the number of active users grew by five times</strong>.</li>
+ <li>12,000 in-app purchases were generated, increasing general e-paper sales by six times,
+ which resulted in <strong>an increase in total magazine circulation</strong>.</li>
+ <li>The digital shopping card was shown more than 200,000 times to redeem offers in shops.</li>
+</ul>
+</p>
+
+<h3>Get started</h3>
+
+<p>
+ Find out more about
+ <a href="https://developer.android.com/google/play/billing/billing_overview.html">
+ in-app purchases</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/drupe-communications.jd b/docs/html/distribute/stories/apps/drupe-communications.jd
new file mode 100644
index 0000000..4284077
--- /dev/null
+++ b/docs/html/distribute/stories/apps/drupe-communications.jd
@@ -0,0 +1,98 @@
+page.title=drupe Launches Android First and Finds Global Success with Beta Testing
+page.metaDescription=Drupe uses beta testing to increase its global reach.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/drupe.jpg
+page.timestamp=1468901832
+
+@jd:body
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/drupe-icon.png" />
+</div>
+
+<h3>
+ Background
+</h3>
+
+<p>
+ In 2015, <a class="external-link" href=
+ "https://play.google.com/store/apps/dev?id=8486231504544197967">
+ drupe mobile</a>, founded by Assaf Ziv and Barak Witkowski, launched the
+ <a class="external-link" href=
+ "https://play.google.com/store/apps/details?id=mobi.drupe.app">drupe app</a>
+ on Android first. With a unique, people-first approach, their communications
+ app is focused on reinventing the way people use their phone to do basic
+ actions. By constantly improving and expanding their cross-app
+ functionality, the app is used globally and was recently awarded Google
+ Play’s Editor’s Choice recognition.
+</p>
+
+<h3>
+ What they did
+</h3>
+
+<dl>
+ <dt><strong>Android openness</strong></dt>
+ <dd>From the start, drupe knew Android was their ideal mobile platform.
+ <em>"Thanks to the openness of the system, we can build a truly native
+ experience on Android. The real way to supply a people-centric experience
+ requires such an openness, not always existing on other platforms,"</em>
+ said Barak Witkowski, co-founder and CEO of drupe. Key to their innovative
+ approach, drupe uses the current context of the user to show them the
+ right contacts and actions to optimize the drupe experience.</dd>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/drupe-screenshot.png">
+</div>
+
+ <dt><strong>Beta testing</strong></dt>
+ <dd>They have a large community of 20,000 <em>druper</em> beta users on
+ Android, which has been critical to their success. To minimize risk and seek
+ feedback from valued users, the team built beta testing into their regular
+ development process. By having a dialogue with users worldwide, they are able
+ to gauge interest in new app versions, collect feature requests, and more.
+ This has helped the team achieve a 99.7% crash-free user ratio, as well
+ as verify new versions in real-life scenarios, on various devices, before
+ full launch.</dd>
+
+ <dt><strong>Going global</strong></dt>
+ <dd>With initial focus on building a high quality app, drupe then set out
+ to take advantage of Android’s global scale. Key to their international
+ growth, the app is now translated in 17 languages, and the store listing
+ page is available in 28 languages. This led to an increase in conversion
+ and retention rates. Additionally, when entering India, the team noticed
+ several user reviews requesting integration with a specific messaging app
+ widely used in the Indian market. Through a combination of this integration,
+ adding Hindi language translation, and other new features, drupe saw improved
+ performance. In six months, <strong>daily active users increased 300%, and
+ actions per average daily user increased 25% in the Indian
+ market</strong>.</dd>
+</dl>
+
+<h3>
+ Results
+</h3>
+
+<p>
+ Drupe’s focus on innovation and building a truly contextual and intuitive
+ app proved to be a model of success for attaining growth in both engagement
+ and new global reach. The team has continued to increase its relevance
+ through feedback loops and feature adoptions. <strong>This has led to 700%
+ growth in daily active users, and 550% growth in number of interactions
+ triggered via drupe</strong> in the past six months. The team is focused on
+ enhancing their recommendation engine to add even more contextual abilities
+ for their users while continuing to grow a successful business on Google
+ Play.
+</p>
+
+<h3>
+ Get started
+</h3>
+
+<p>
+ Learn more about
+ <a href="{@docRoot}distribute/engage/beta.html">beta testing</a>
+ and find out how to
+ <a href="{@docRoot}distribute/tools/localization-checklist.html">
+ localize your app</a> to create a high-quality experience for global users.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/economist-espresso.jd b/docs/html/distribute/stories/apps/economist-espresso.jd
new file mode 100644
index 0000000..441393b
--- /dev/null
+++ b/docs/html/distribute/stories/apps/economist-espresso.jd
@@ -0,0 +1,70 @@
+page.title=The Economist Espresso Increases Ratings by Launching Rating Requests
+page.metaDescription=The Economist improves ratings through user participation.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/economist-espresso.png
+page.timestamp=null
+
+@jd:body
+
+
+<h3>Background</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/economist-espresso-icon.png" />
+</div>
+
+<p>
+ <a class="external-link" href="https://play.google.com/store/apps/details?id=uk.co.economist">
+ The Economist</a> launched the
+ <a class="external-link" href="https://play.google.com/store/apps/details?id=com.economist.darwin">
+ Espresso</a> app in November 2014. Espresso offers a morning briefing from the editors of The
+ Economist, six days a week. Delivered to readers’ mobile phones first thing in the morning, it
+ provides an overview of the global agenda for the coming day. It informs readers about what to
+ look out for in business, finance, and politics, and most importantly, what to make of it.
+</p>
+
+<p>
+ While the app received a lot of positive feedback from users on traditional customer support
+ channels, it received less feedback through direct app reviews. The Economist decided to run
+ tests to increase app reviews, resulting in improved ratings.
+
+<h3>What they did</h3>
+
+<p>
+ In April 2016, The Economist began testing to determine if asking for reviews would improve
+ user participation. They introduced rating requests into the app whereby users received a
+ notification asking them to rate the app while using it.
+</p>
+
+<p>
+ They prompted only users who had fully experienced the app, notifying those who had read more
+ than 25 articles after using it for more than a week. The prompt text was branded:
+ <em>Are you enjoying the Economist Espresso?</em> Upon clicking <em>yes</em>, the user was
+ taken to the Google Play store to review and rate the app.
+
+<h3>Results</h3>
+
+<p>
+ By capturing readers’ feedback in the Play store, The Economist was able to share the goodwill
+ and positive sentiment, <strong>further increasing its star rating and the number of app
+ installs</strong>.
+</p>
+
+<p>
+ After just one week following the launch of rating requests, The Espresso app's star rating
+ increased by 5%, with <strong>the average number of ratings received growing 40 times</strong>.
+</p>
+
+<h3>Get started</h3>
+
+<p>
+ Find out more about
+ <a class="external-link" href="https://support.google.com/googleplay/android-developer/answer/138230">
+ ratings and reviews</a>.
+</p>
+
+<p>
+ Get best practices for news publishers in
+ <a class="external-link" href="https://play.google.com/store/books/details/Google_Inc_The_News_Publisher_Playbook_for_Android?id=O7T3CwAAQBAJ&hl=en_GB&e=-EnableAppDetailsPageRedesign">
+ The News Publisher Playbook (for Android development)</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/expressen-sports.jd b/docs/html/distribute/stories/apps/expressen-sports.jd
new file mode 100644
index 0000000..b53cb62
--- /dev/null
+++ b/docs/html/distribute/stories/apps/expressen-sports.jd
@@ -0,0 +1,57 @@
+page.title=Expressen Sport App Improves Content Engagement with New Onboarding and Navigation
+page.metaDescription=Expressen enhances their Sport app.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/expressen-sport.png
+page.timestamp=null
+
+@jd:body
+
+
+<h3>Background</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/expressen-icon.png" />
+</div>
+
+<p>
+ In January 2016,
+ <a class="external-link" href="https://play.google.com/store/apps/details?id=se.expressen.launcher&hl=en">
+ Expressen</a> launched a new sports app to reach sports enthusiasts directly
+ and to better optimize the app for sports content. They decided to analyze
+ users' behavior by looking at user paths in existing sports content,
+ combined with user research and testing various prototypes with real users.
+ They found that readers have different needs and preferences. For example,
+ some people like a specific sport, league, or player that others have no
+ interest in. Following these results, they integrated two main changes to
+ increase appeal to different types of readers.
+</p>
+
+<h3>What they did</h3>
+
+<p>
+ Expressen introduced a new onboarding flow that allows users to select the
+ type of push notifications they want to subscribe to. They also implemented
+ contextual navigation where the top header navigational links change,
+ showing the most relevant links to the reader at that moment in time. For
+ example, if you're reading about football, relevant links about that sport
+ are displayed.
+</p>
+
+<h3>Results</h3>
+
+<p>
+ After the new release of the app, <strong>results showed a higher opt-in
+ rate for push notifications in the Sport app (+16.9%)</strong> compared to
+ their main app, and content consumption increased +7% for page views and
+ +8.3% for video views.
+</p>
+
+<h3>Get started</h3>
+
+<p>
+ Learn more about the
+ <a href="https://developer.android.com/training/tv/playback/onboarding.html?hl=mk">
+ user onboarding flow</a> and find out how to
+ <a href="https://developer.android.com/design/patterns/navigation.html">
+ implement contextual navigation</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/lifesum-health.jd b/docs/html/distribute/stories/apps/lifesum-health.jd
new file mode 100644
index 0000000..2d3f203
--- /dev/null
+++ b/docs/html/distribute/stories/apps/lifesum-health.jd
@@ -0,0 +1,60 @@
+page.title=Lifesum Doubles Retention of Google Fit Users Following Integration on Android
+page.metaDescription=Lifesum integrates Google Fit into their app.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/lifesum.png
+page.timestamp=null
+
+@jd:body
+
+
+<h3>Background</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/lifesum-icon.png" />
+</div>
+
+<p>
+ <a class="external-link" href="https://play.google.com/store/apps/details?id=com.sillens.shapeupclub">
+ Lifesum</a> is a health and fitness app from Sweden that was launched on Android in 2012.
+ Since then, the app has had more than five million installs on Android, and Lifesum collaborated
+ with Google for the launch of <a class="external-link" href="http://www.google.com/fit/">
+ Google Fit</a> in 2014. Google Fit soon became a key component of user activity outside the
+ app and has enabled Lifesum to scale partner integrations, accelerate development cycle, and
+ increase user satisfaction and engagement.
+</p>
+
+<h3>What they did</h3>
+
+<p>
+ Lifesum integrated Google Fit APIs to gather more insightful data, leading to a shift in
+ focus from simply gathering large amounts of user data to actual analysis of it. Google Fit has
+ also made direct integrations with partners much easier to scale and sometimes even
+ unnecessary, and has largely reduced app development time. Lifesum used findings from the
+ integration to launch their second app, <em>Movesum</em>, a step-counter app that imports steps
+ and calories and displays the information in a fun way. Thanks to the integration, the app was
+ developed in just two weeks.
+</p>
+
+<h3>Results</h3>
+
+<p>
+ Lifesum’s users now actively request integration with Google Fit, resulting in an improvement
+ in the app's ratings and reviews on the Google Play store. Engagement is also much higher for
+ Google Fit-connected users, whose <strong>retention rate is twice that of other Android
+ users</strong>. User retention on Android is 5-10% better than on other platforms.
+</p>
+
+<p>
+ Joakim Hammer, Android developer at Lifesum, says "Google Fit is our infrastructure for
+ integrating with other apps. It's great for the user as it increases the trustworthiness of the
+ data. Personally, it’s been a great experience leading the integration. The implementation was
+ fast and easy, and it has helped us with everything from product development and user
+ engagement, to partnerships."
+</p>
+
+<h3>Get started</h3>
+
+<p>
+ Find out more about <a class="external-link" href="https://developers.google.com/fit/">
+ The Google Fit SDK</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/noom-health.jd b/docs/html/distribute/stories/apps/noom-health.jd
new file mode 100644
index 0000000..c99efac
--- /dev/null
+++ b/docs/html/distribute/stories/apps/noom-health.jd
@@ -0,0 +1,115 @@
+page.title=Noom Grows International Revenue by 80% Through Localization on Google Play
+page.metaDescription=Noom increases revenue by localizing their app.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/noom.jpg
+page.timestamp=1468901832
+
+@jd:body
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/noom-icon.png" />
+</div>
+
+<h3>
+ Background
+</h3>
+
+<p>
+ With a mission to help people live healthier lives,
+ <a class="external-link" href=
+ "https://play.google.com/store/apps/developer?id=Noom+Inc.">Noom</a> guides
+ their users through behavior change programs to create lifestyle habits and
+ target global health challenges. Available initially on Google Play,
+ Android first and Noom have achieved success expanding to international
+ markets, taking advantage of the broad reach of Android.
+</p>
+
+<h3>
+ What they did
+</h3>
+
+<p>
+ Launching first in the US, Noom created a series of programs tailored to
+ their users’ specific <a class="external-link" href=
+ "https://play.google.com/store/apps/details?id=com.wsl.noom">
+ health goals</a>. Key to their approach is offering a holistic solution,
+ including simple personalized tasks, progress tracking, meal feedback, and
+ support from both personal coaches and peers. The team has a strategic
+ approach to expanding their user base globally. Noom localized their app to
+ better connect with users in the following areas:
+</p>
+
+<ul>
+ <li><strong>Localized product</strong>
+ <ul style="list-style: none;">
+ <li>In addition to translating their app to five languages and their store
+ listing page to 11 languages, Noom conducted extensive analysis to
+ determine the right financial model tailored to each international
+ market. This included evaluation of their competitive landscape and
+ local health and wellness spending behavior, in addition to running
+ pricing experiments to determine the optimal offering between
+ subscriptions, IAPs, or a premium app.</li>
+ </ul>
+ </li>
+
+ <li><strong>Localized cuisines</strong>
+ <ul style="list-style: none;">
+ <li>When Noom started researching the Korean, Japanese, German, and Latin
+ American markets, they immediately focused on localizing their food
+ database. Using a combination of local food editors, existing food
+ databases, and user suggestions, their app now includes local cuisine
+ and popular packaged food brands, offering a simpler and more
+ comprehensive experience for users.</li>
+ </ul>
+ </li>
+
+ <li><strong>Localized coaches</strong>
+ <ul style="list-style: none;">
+ <li>Hiring local coaches not only removed language barriers, but also
+ reduced response times as they are located within the same time zone
+ as their users. Using various notification types, Noom has increased
+ user engagement by three to four times.</li>
+ </ul>
+ </li>
+</ul>
+
+<img src="{@docRoot}images/distribute/stories/noom-screenshot.png">
+ <p class="img-caption">
+ <strong>Figure 1.</strong> German Play Store listing page, Korean recipes,
+ and Japanese meal plan
+ </p>
+
+<h3>
+ Results
+</h3>
+
+<p>
+ <em>"Android's global focus and great localization tooling made the decision
+ to go global much easier. Localization to new markets has been a consistent
+ growth driver at Noom,"</em> said Artem Petakov, co-founder and President
+ at Noom.
+</p>
+
+<p>
+ Over the last three years, Noom’s localization efforts led to an <strong>80%
+ increase in international revenue growth on Android</strong>. In Japan
+ alone, <strong>revenue increased more than 480%</strong> during the same
+ time period. To identify future expansion opportunities, the team looks
+ towards countries with strong Android penetration and install growth
+ using the English product and plans to apply their localization methods to
+ achieve even greater success.
+</p>
+
+<h3>
+ Get started
+</h3>
+
+<p>
+ Learn more about <a class="external-link" href=
+ "https://material.google.com/patterns/notifications.html">Notifications</a>,
+ and find out about
+ <a href="{@docRoot}distribute/tools/localization-checklist.html">
+ app localization</a> and how to
+ <a href="{@docRoot}distribute/users/expand-to-new-markets.html">
+ Expand Into New Markets</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/games.jd b/docs/html/distribute/stories/games.jd
index daaac0d..cd31aae 100644
--- a/docs/html/distribute/stories/games.jd
+++ b/docs/html/distribute/stories/games.jd
@@ -25,8 +25,7 @@
<h2 class="norule">Articles</h2>
<div class="resource-widget resource-flow-layout col-16"
- data-query="type:distribute+tag:developerstory+tag:games"
- data-sortOrder="-timestamp"
+ data-query="collection:distribute/stories/games/docs"
data-cardSizes="6x6"
data-items-per-page="15"
data-initial-results="6"></div>
diff --git a/docs/html/distribute/stories/games/animoca-star-girl.jd b/docs/html/distribute/stories/games/animoca-star-girl.jd
new file mode 100644
index 0000000..a38eed2
--- /dev/null
+++ b/docs/html/distribute/stories/games/animoca-star-girl.jd
@@ -0,0 +1,89 @@
+page.title=Star Girl Increases In-App Purchases by 3.5X Through More Flexible Minimum Pricing on Google Play
+page.metaDescription=Star Girl Increases In-App Purchases by 3.5X.
+page.tags="developerstory", "games", "googleplay", "google play"
+page.image=images/cards/distribute/stories/animoca.jpg
+
+@jd:body
+
+<style type="text/css">
+ span.positive{
+ color:green;
+ font-size: 125%;
+ font-weight:bold;">
+ }
+ span.negative{
+ color:red;
+ font-size: 125%;
+ font-weight:bold;">
+ }
+</style>
+
+<h3>Background</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/animoca-logo.png" />
+</div>
+
+<p>
+ <a class="external-link"
+ href="https://play.google.com/store/apps/details?id=com.animoca.google.starGirl&hl=en&e=-EnableAppDetailsPageRedesign">
+ Star Girl</a> is a series of SIM/role playing games published by <a class="external-link"
+ href="https://play.google.com/store/apps/dev?id=8271704752057011334&hl=en&e=-EnableAppDetailsPageRedesign">
+ Animoca</a>, a Hong Kong based game developer. The Star Girl series has more than 70 million
+ downloads and is localized in 18 languages. With a fast-growing user base in markets
+ including SEA, India, and Latin America, Animoca is exploring ways to effectively increase
+ monetization with a localized pricing strategy.
+</p>
+
+<h3>What they did</h3>
+
+<p>
+ Following the introduction of
+ <a class="external-link" href="http://android-developers.blogspot.com/2015/11/minimum-purchase-price-for-apps-and-in.html">
+ more flexible minimum pricing</a> in November 2015, Animoca took the opportunity to test
+ sachet pricing models across Thailand, Malaysia, Philippines, Indonesia, Brazil, and Russia:
+</p>
+
+<p>
+ <img src="{@docRoot}images/distribute/stories/animoca-flow.jpg" />
+</p>
+
+<p>
+ Animoca created a new sachet SKU, which offered 100 diamonds and 5,000 coins, at the new lower
+ minimum price available in these markets. The new SKU is approximately 60% cheaper than the
+ previous minimum-priced product and is accessible only through geo-targeted, in-game
+ banners in localized languages.
+</p>
+
+<h3>Results</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/animoca-graph.jpg" />
+</div>
+
+<p>
+ The changes to minimum prices across these markets resulted in positive results, with the
+ number of transactions increasing 3.5X in the three months following launch.
+</p>
+
+<p>
+ Also, 90% of these transactions were first-time new buyers, half of which
+ followed up with purchases of regular packages. This helps to create a more sustainable revenue
+ impact, as described by Yusuf Goolamabbas, CTO of Animoca:
+</p>
+
+<p>
+ <em>“Sachet marketing has made IAPs more affordable to users in emerging markets. We are seeing
+ significant growth in new buyers as well as returning buyers and a positive impact on revenue in
+ emerging markets.”</em>
+</p>
+
+<h3>Get started</h3>
+
+<p>
+ <a class="external-link" href="https://support.google.com/googleplay/android-developer/answer/6334373?hl=en-GB">
+ Learn more about flexible minimum pricing</a> and
+ <a class="external-link" href="http://g.co/play/playbook-dac-writtenstudies-evergreen">
+ get the Playbook for Developers app</a> to grow your business and improve
+ monetization with Google Play.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/games/happy-labs-experiment.jd b/docs/html/distribute/stories/games/happy-labs-experiment.jd
new file mode 100644
index 0000000..e317e21
--- /dev/null
+++ b/docs/html/distribute/stories/games/happy-labs-experiment.jd
@@ -0,0 +1,105 @@
+page.title=Happy Labs Increases Installs by 32% on Google Play with Store Listing Experiments
+page.metaDescription=Happy Labs Increases Installs by 32%.
+page.tags="developerstory", "games", "googleplay", "google play"
+page.image=images/cards/distribute/stories/happylabs-logo.png
+
+@jd:body
+
+<style type="text/css">
+ span.positive{
+ color:green;
+ font-size: 125%;
+ font-weight:bold;">
+ }
+ span.negative{
+ color:red;
+ font-size: 125%;
+ font-weight:bold;">
+ }
+</style>
+
+<h3>Background</h3>
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/happylabs-logo.png" />
+</div>
+
+<p>
+ <a class="external-link"
+ href="https://play.google.com/store/apps/dev?id=5211519071117278745&hl=en">
+ Happy Labs</a>, founded in 2012, is a successful game developer in Southeast Asia with 13
+ game titles and over 30 million downloads. Its flagship free-to-play virtual sim game,
+ <a class="external-link"
+ href="https://play.google.com/store/apps/dev?id=5211519071117278745&hl=en">Happy Pet Story</a>,
+ launched in February 2016 and is designed for female gamers of all ages.
+</p>
+
+<p>
+ Following the announcement of Store Listing Experiments in May, Happy Labs decided to test
+ variations of their game icon and screenshots to optimize their store listing.
+</p>
+
+<h3>What they did</h3>
+
+<p>
+ Happy Labs used the Store Listing Experiments feature in the Google Play Developer Console to
+ test three new variations of their new game icon. Encouraged by early results, they then
+ optimized the game screenshots displayed in their store listing.
+</p>
+
+<h3>Results</h3>
+
+<p>
+ The results showed that the new icon, <em>Sweet Bubbles</em> without a frame, outperformed
+ the initial <em>Mojo</em> icon and two other variants, with a 38.6% increase in installs:
+</p>
+
+<p>
+ <img src="{@docRoot}images/distribute/stories/happylabs-happy_pet_icon.png" />
+</p>
+
+<p>
+ Based on these findings, Happy Labs changed the Happy Pet Story icon to the new image globally
+ on Google Play.
+</p>
+
+<p>
+ Following the positive icon testing results, Happy Labs ran global store listing experiments
+ with a combination of screenshots from their store listing over a five week period. They then took
+ their best performing screenshots from the experiments and tested them across three specific
+ countries: Indonesia, Thailand, and Japan. The results showed an average increase of 19.87% in
+ installs.
+</p>
+
+<p>
+ Migrating from the original images to the variants shown in the following figure increased organic
+ installs in Thailand by 13.9%:
+</p>
+
+<p>
+ <img src="{@docRoot}images/distribute/stories/happylabs-variant.png" />
+</p>
+
+<p>
+ With the combination of both store listing experiments, Happy Pet Story saw an average increase of
+ 32% in month-on-month organic daily downloads globally, as described by Jeffrey Chee, CEO of Happy
+ Labs:
+</p>
+
+<p>
+ <em>“Store listing experiments have been an invaluable tool for us in optimizing our Play store
+ presence. I am really happy that we were able to get an uplift of 32% in organic installs with
+ minimal investment in terms of resources.”</em>
+</p>
+
+<h3>Get started</h3>
+
+<p>
+ Learn how to run
+ <a class="external-link" href="https://support.google.com/googleplay/android-developer/answer/6227309">
+ Store Listing Experiments</a> and read our best practices for
+ <a href="https://developer.android.com/distribute/users/experiments.html">
+ running successful experiments</a>. For more best practices on growing your business with Google
+ Play, <a class="external-link" href="http://g.co/play/playbook-dac-writtenstudies-evergreen">
+ get the Playbook for Developers app</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/games/playlab-puzzles.jd b/docs/html/distribute/stories/games/playlab-puzzles.jd
new file mode 100644
index 0000000..ef1ccff
--- /dev/null
+++ b/docs/html/distribute/stories/games/playlab-puzzles.jd
@@ -0,0 +1,87 @@
+page.title=Playlab Increases Juice Cubes Conversions by 25% with Store Listing Experiments
+page.metaDescription=Playlab uses store listing experiments to refresh their Juice Cubes icon.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/playlab.jpg
+page.timestamp=1468901832
+
+@jd:body
+
+<div class="figure">
+ <img src="{@docRoot}images/distribute/stories/playlab-icon.png" />
+</div>
+
+<h3>
+ Background
+</h3>
+
+<p>
+ <a class="external-link" href=
+ "https://play.google.com/store/apps/dev?id=9190927840679184784&e=-EnableAppDetailsPageRedesign">
+ Playlab</a> is a game developer and publisher based in Hong Kong, with
+ production studios in Bangkok and Manila. Playlab apps include
+ <a class="external-link" href=
+ "https://play.google.com/store/apps/details?id=ppl.unity.JuiceCubesBeta">
+ Juice Cubes</a>, <a class="external-link" href=
+ "https://play.google.com/store/apps/details?id=ppl.unity.junglecubes">
+ Jungle Cubes</a>, and <a class="external-link" href=
+ "https://play.google.com/store/apps/details?id=ppl.cocos2dx.ranchrun&hl=en">
+ Ranch Run</a>.
+
+ Released in 2013, <a class="external-link" href=
+ "https://play.google.com/store/apps/details?id=ppl.unity.JuiceCubesBeta">
+ Juice Cubes</a> is a strategy puzzle game with over 25 million downloads
+ worldwide and over 100,000 five-star reviews on Google Play. The game has
+ gained success in Southeast Asian markets such as Indonesia and Thailand,
+ and in Western markets such as the US, Australia, the UK, and Canada.
+</p>
+
+<h3>
+ What they did
+</h3>
+
+<p>
+ As part of Juice Cubes’ content update in April 2016, Playlab decided to
+ refresh its Play Store icon and test whether different icons could increase
+ their conversion rate.
+</p>
+
+<p>
+ Playlab used the <em>Store Listing Experiments</em> feature on the Google
+ Play Developer Console to test three variations of their new game icon.
+</p>
+
+<h3>
+ Results
+</h3>
+
+<p>
+ Within three days of running the store listing experiment, Playlab saw
+ positive results which led them to make an informed decision to change the
+ current icon to the best-performing version. Variant C outperformed the
+ initial control icon and the two other variants, with a <strong>25% increase
+ in installs</strong>. One month after applying the best-performing icon,
+ Juice Cubes continued to see a 25% increase in the number of conversions
+ from store visitors who made organic installs.
+</p>
+
+<img src="{@docRoot}images/distribute/stories/playlab-screenshot.png">
+
+<p>
+ <em>"Google Play Store Listing Experiments offer a fast, easy, and free way
+ to do A/B testing on an icon. The data provided after each test helped us
+ to make a decision really quickly. The best part is the team no longer needs
+ so many resources to decide which icon to use because we can let the
+ users decide."</em> said Jakob Lykkegaard, CEO and co-founder of Playlab.
+</p>
+
+<h3>
+ Get started
+</h3>
+
+<p>
+ Learn how to use <a class="external-link" href=
+ "https://support.google.com/googleplay/android-developer/answer/6227309">
+ A/B testing</a> and find out more about how to run
+ <a href="{@docRoot}distribute/users/experiments.html">
+ store listing experiments</a> on Google Play.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/index.jd b/docs/html/distribute/stories/index.jd
index 1745535..7d84ce4 100644
--- a/docs/html/distribute/stories/index.jd
+++ b/docs/html/distribute/stories/index.jd
@@ -21,11 +21,11 @@
<h3 class="norule">Articles</h3>
<div class="resource-widget resource-flow-layout col-16"
- data-query="type:distribute+tag:developerstory+tag:apps"
- data-sortOrder="-timestamp"
+ data-query="collection:distribute/stories/apps/docs"
data-cardSizes="6x6"
data-items-per-page="15"
data-initial-results="6"></div>
+
</div></section>
<section class="dac-section dac-small" id="latest-games"><div class="wrap">
@@ -43,9 +43,9 @@
<h3 class="norule">Articles</h3>
<div class="resource-widget resource-flow-layout col-16"
- data-query="type:distribute+tag:developerstory+tag:games"
- data-sortOrder="-timestamp"
+ data-query="collection:distribute/stories/games/docs"
data-cardSizes="6x6"
data-items-per-page="15"
data-initial-results="6"></div>
+
</div></section>
diff --git a/docs/html/google/play/billing/billing_admin.jd b/docs/html/google/play/billing/billing_admin.jd
index ad09f1f..9936489 100644
--- a/docs/html/google/play/billing/billing_admin.jd
+++ b/docs/html/google/play/billing/billing_admin.jd
@@ -51,9 +51,9 @@
listed on an app's product list. Each app has its own product list; you cannot sell
items that appear on another app's product list.</p>
-<p>You can access an app's product list by opening the <strong>In-app Products</strong>
+<p>You can access an app's product list by opening the <em>In-app Products</em>
page for an app that is listed in your developer account. The link to the
-<strong>In-app Products</strong> page appears only if you have a Google payments merchant
+<em>In-app Products</em> page appears only if you have a Google payments merchant
account and the app's manifest includes the
<code>com.android.vending.BILLING</code> permission. For more information about this
permission, see <a href="{@docRoot}google/play/billing/billing_integrate.html#billing-permission">
@@ -73,7 +73,7 @@
supported; instead, you must publish it to the alpha or beta distribution
channel. For more information, see <a
href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
-are No Longer Supported</a>.
+are No Longer Supported</a>.</p>
<p>In addition, an app package can have only one product list. If you create a product
list for an app, and you use the <a
@@ -82,8 +82,8 @@
associated with the app listing. You cannot create individual product lists for each APK if
you are using the multiple APK feature.</p>
-<p>You can add items to a product list two ways: you can add items one at a time on the <strong>In-app
-Products</strong> page, or you can add a batch of items by importing the items from a
+<p>You can add items to a product list two ways: you can add items one at a time on the <em>In-app
+Products</em> page, or you can add a batch of items by importing the items from a
comma-separated values (CSV) file. Adding items one at a time is useful if your
app has only a few in-app items or you are adding only a few items to a
product list for testing purposes. The CSV file method is useful if your app has a large
@@ -100,14 +100,14 @@
<ol>
<li><a href="http://play.google.com/apps/publish">Log in</a> to your publisher account.</li>
- <li>In the <strong>All applications</strong> panel, click on the
- app name, then open the <strong>In-app Products</strong> page.</li>
+ <li>In the <em>All applications</em> panel, click on the
+ app name, then open the <em>In-app Products</em> page.</li>
<li><p>Click <strong>Add new product</strong>. After you provide the product type and ID for the item you are
selling, click <strong>Continue</strong>.</p>
<dl>
<dt>Product Type</dt>
<dd>
- <p>The product type can be <strong>Managed product</strong> or <strong>Subscription</strong>. You cannot
+ <p>The product type can be "Managed product" or "Subscription." You cannot
change an item's product type after you create the item. For more information, see
<a href="#billing-purchase-type">Choosing a Product Type</a>.</p>
<p class="note"><strong>Note: </strong>For subscription items, you cannot change the
@@ -169,7 +169,7 @@
<p>You can also change prices for other currencies manually, but you can do
this only if a currency is used in one of the target countries for your
app. You can specify target countries for your app on the
- <strong>Pricing & Distribution</strong> page in the Google Play
+ <em>Pricing & Distribution</em> page in the Google Play
Developer Console.</p>
</dd>
</dl>
@@ -187,266 +187,412 @@
<h3 id="billing-bulk-add">Adding a batch of items to a product list</h3>
-<p>To add a batch of items to a product list using a CSV file, you first need to create your CSV
-file. The data values that you specify in the CSV file represent the same data values you specify
-manually through the In-app Products UI (see <a href="#billing-form-add">Adding items one at a time
-to a product list</a>).
+<p>To add a batch of items to a product list using a CSV file, you first need to
+create your CSV file. The data values that you specify in the CSV file represent
+the options that you set when adding in-app products to a product list using the
+Google Play Developer Console UI. For more information about using this UI, see
+<a href="#billing-form-add">Adding items one at a time to a product list</a>.
-<p>If you are importing and exporting CSV files with in-app products, keep
-country-specific pricing in mind. If you use auto-fill, you can provide a
-tax-exclusive default price, and tax-inclusive prices will be auto-filled. If you
-do not use auto-fill, prices you provide must include tax.</p>
+<p class="note"><strong>Note:</strong> Batch upload of in-app product lists
+containing subscriptions is not supported. Also, when updating existing items in
+a batch upload, you cannot include changes to in-app products that are linked to
+a <a href="#pricing-template">pricing template</a>.</p>
-<p class="note"><strong>Note:</strong> Batch upload of product lists containing
-subscriptions is not supported. Also, when updating existing items in a batch
-upload, you cannot include changes to in-app products that are linked to a
-<a href="#pricing-template">pricing template</a>.</p>
-
-<p>To import the items that are specified in your CSV file, do the following:</p>
+<p>To import the in-app products that are specified in your CSV file, do the
+following:</p>
<ol>
- <li><a href="http://play.google.com/apps/publish">Log in</a> to your publisher account.</li>
- <li>In the <strong>All applications</strong> panel, select the app
- name, then open the <strong>In-app Products</strong> page.</li>
- <li>On the In-app Products List page, click <strong>Import/Export</strong>
- > <strong>Import in-app products from CSV file</strong>, then select your
- CSV file.
- <p>The CSV file must be on your local computer or on a local disk that is connected to your
- computer.</p>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your
+ publisher account.
</li>
- <li>Select the <strong>Overwrite</strong> checkbox if you want to overwrite existing items in
- your product list.
- <p>This option overwrites values of existing items only if the value of the <em>product_id</em>
- in the CSV file matches the In-app Product ID for an existing item in the product list.
- Overwriting doesn't delete items that are on a product list but not present in the CSV
- file.</p>
+ <li>In the <em>All applications</em> panel, select the app
+ name, then open the <em>In-app Products</em> page.</li>
+ <li>
+ <p>On the <em>In-app Products</em> page, click
+ <strong>Import/Export</strong> > <strong>Import in-app products from CSV
+ file</strong> to open the <em>Import In-app Products</em> dialog.</p>
+ </li>
+ <li>
+ <p>
+ If you want to overwrite existing in-app products in your product list
+ during the import process, select the <strong>Overwrite existing
+ products</strong> checkbox.
+ </p>
+ <p>
+ This option overwrites values of existing items only if the value of the
+ <code>Product ID</code> in the CSV file matches the in-app product ID for
+ an existing in-app product in the product list. The overwriting process
+ doesn't delete in-app products that exist in a product list but aren't
+ included in the CSV file
+ </p>
+ <p class="note"><strong>Note: </strong>If you choose not to overwrite
+ existing items, the <code>Product ID</code> given to each item in the CSV
+ file must be different from any of the <code>Product ID</code> values
+ assigned to existing in-app products.
+ </p>
+ </li>
+ <li>
+ Select <strong>Browse files</strong>, then choose the CSV file that contains
+ the items you want to import. The CSV file must be stored locally.
</li>
</ol>
-<p>You can also export an existing product list to a CSV file by clicking <strong>Export to CSV
-</strong> on the In-app Product List page. This is useful if you have manually added items to
-a product list and you want to start managing the product list through a CSV file.</p>
+<p>
+ You can also export an existing product list to a CSV file by clicking
+ <strong>Import/Export</strong> > <strong>Export in-app products to CSV file
+ </strong> on the <em>In-app Products page</em>. This is useful if you have
+ used the UI to add in-app products to your app but you want to start managing
+ the product list through a CSV file instead.
+</p>
<h4 id="billing-bulk-format">Formatting batches of items</h4>
-<p>The CSV file uses commas (,) and semicolons (;) to separate data values.
-Commas are used to separate primary data values, and semicolons are used to
-separate subvalues. For example, the syntax for the CSV file is as follows:</p>
-
-<p>"<em>product_id</em>","<em>publish_state</em>","<em>purchase_type</em>","<em>autotranslate</em>
-","<em>locale</em>; <em>title</em>; <em>description</em>","<em>autofill</em>","<em>country</em>;
-<em>price</em>", "<em>pricing_template_id</em>"
+<p>
+ The CSV file uses commas (<code>,</code>) and semicolons (<code>;</code>) to
+ separate data values. Commas are used to separate primary data values, and
+ semicolons are used to separate subvalues. Each item must appear entirely on a
+ single line within the CSV file.
+</p>
+<p>
+ When creating a CSV file that represents a list of items, you must specify the
+ CSV syntax on the first row, followed by the items themselves on subsequent
+ rows, as shown in the following example:
</p>
-<p>Descriptions and usage details are provided below.</p>
+<pre class="no-pretty-print">
+Product ID,Published State,Purchase Type,Auto Translate,Locale; Title; Description,Auto Fill Prices,Price,Pricing Template ID
+basic_sleeping_potion,published,managed_by_android,false,en_US; Basic Sleeping Potion; Puts small creatures to sleep.; es_ES; Poción básica de dormir; Causa las criaturas pequeñas ir a dormir.,false,,4637138456024710495
+standard_sleeping_potion,published,managed_by_android,false,en_US; Standard Sleeping Potion; Puts all creatures to sleep for 2 minutes.,true, 1990000,
+invisibility_potion,published,managed_by_android,false,en_US; Invisibility Potion; Invisible to all enemies for 5 minutes.,false, US; 1990000; BR; 6990000; RU; 129000000; IN; 130000000; ID; 27000000000; MX; 37000000;
+</pre>
+
+<p>
+ This example contains details for three items, each of which represents an
+ in-app product:
+</p>
+<ul>
+ <li>
+ The first item defines a title and description for the <code>en_US</code>
+ and <code>es_ES</code> locales. A pricing template defines the item's
+ price.
+ </li>
+ <li>
+ The second item doesn't use a pricing template. Instead, it specifies a
+ price for the default country (US). The Google Play Developer Console
+ uses current exchange rates and locally relevant pricing patterns to
+ automatically set the prices in all other countries where the app is
+ distributed.
+ </li>
+ <li>
+ The third item also doesn't use a pricing template. The item's price is
+ specified manually for each country where the app is distributed.
+ </li>
+</ul>
+
+<p>
+ Each row in a CSV file can contain the following values, though at least one
+ of these values is undefined in each row:
+</p>
<dl>
- <dt>product_id</dt>
- <dd>
- This is equivalent to the In-app Product ID setting in the In-app Products UI. If you specify
- a <em>product_id</em> that already exists in a product list, and you choose to overwrite
- the product list while importing the CSV file, the data for the existing item is overwritten with
- the values specified in the CSV file. The overwrite feature does not delete items that are on a
- product list but not present in the CSV file.
- </dd>
- <dt>publish_state</dt>
- <dd>
- This is equivalent to the Publishing State setting in the In-app Products UI. Can be <code>
- published</code> or <code>unpublished</code>.
- </dd>
- <dt>purchase_type</dt>
- <dd>
- This is equivalent to the Product Type setting in the In-app Products UI. Can be <code>
- managed_by_android</code>, which is equivalent to <strong>Managed per user account
- </strong> in the In-app Products UI, or <code>managed_by_publisher</code>, which is equivalent
- to <strong>Unmanaged</strong> in the In-app Products UI.
- </dd>
- <dt>autotranslate</dt>
- <dd>
- This is equivalent to selecting the <strong>Fill fields with auto translation</strong>
- checkbox in the In-app Products UI. Can be <code>true</code> or <code>false</code>.
- </dd>
- <dt>locale</dt>
- <dd>
- <p>This is equivalent to the Language setting in the In-app Products UI. You must have an entry
- for the default locale. The default locale must be the first entry in the list of
- locales, and it must include a <em>title</em> and <em>description</em>. If you want to provide
- translated versions of the <em>title</em> and <em>description</em> in addition to the default,
- you must use the following syntax rules:</p>
- <ul>
- <li>
- <p>If <em>autotranslate</em> is <code>true</code>, you must specify the default locale,
- default title, default description, and other locales using the following format:</p>
- <p>"true,"<em>default_locale</em>; <em>default_locale_title</em>;
- <em>default_locale_description</em>; <em>locale_2</em>; <em>locale_3</em>, ..."</p>
- </li>
- <li>
- <p>If <em>autotranslate</em> is <code>false</code>, you must specify the default locale,
- default title, and default description as well as the translated titles and descriptions using
- the following format:</p>
- <p>"false,"<em>default_locale</em>; <em>default_locale_title</em>;
- <em>default_locale_description</em>; <em>locale_2</em>; <em>locale_2_title</em>;
- <em>local_2_description</em>; <em>locale_3</em>; <em>locale_3_title</em>;
- <em>locale_3_description</em>; ..."</p>
- </li>
- </ul>
- <p>See table 1 for a list of the language codes you can use with the <em>locale</em> field.</p>
- </dd>
- <dt>title</dt>
- <dd>
- This is equivalent to the Title setting in the In-app Products UI. If the <em>title</em>
- contains a semicolon, it must be escaped with a backslash (for example, <code>\;</code>). Also, a backslash
- must be escaped with a backslash (for example, <code>\\</code>).
- </dd>
- <dt>description</dt>
- <dd>
- This is equivalent to the Description in the In-app Products UI. If the <em>description</em>
- contains a semicolon, it must be escaped with a backslash (for example, <code>\;</code>). Also, a backslash
- must be escaped with a backslash (for example, <code>\\</code>).
- </dd>
- <dt>autofill</dt>
- <dd>
- <p>This is equivalent to clicking <strong>Auto Fill</strong> in the In-app Products UI. Can be
- <code>true</code> or <code>false</code>. The syntax for specifying the <em>country</em>
- and <em>price</em> varies depending on which <em>autofill</em> setting you use:</p>
- <ul>
- <li>
- <p>If <em>autofill</em> is set to <code>true</code>, you need to specify only the default
- price in your home currency, and you must use this syntax:</p>
- <p>"true","<em>default_price_in_home_currency</em>"
- </li>
- <li>
- <p>If <em>autofill</em> is set to <code>false</code>, you need to either specify the <em>pricing_template_id</em>
- that is linked to the in-app product or specify a <em>country</em> and a <em>price</em> for each currency.
- If you choose to specify countries and prices, you must use the following syntax:</p>
- <p>"false", "<em>home_country</em>; <em>default_price_in_home_currency</em>; <em>country_2</em>;
- <em>country_2_price</em>; <em>country_3</em>; <em>country_3_price</em>; ..."</p>
- </li>
- </ul>
- <p class="note"><strong>Note: </strong>If you use an <em>autofill</em> value of <code>false</code>
- and set country prices manually, you must incorporate country-specific
- pricing patterns, including tax rates, into the prices you provide.</p>
- </dd>
- <dt>country</dt>
- <dd>
- The country for which you are specifying a price. You can only list countries that your
- app is targeting. The country codes are two-letter uppercase
- ISO country codes (such as "US"), as defined by
- <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-2</a>.
- </dd>
- <dt>price</dt>
+ <dt><code>Product ID</code></dt>
<dd>
<p>
- If you use this value, you shouldn't specify a value for the <em>pricing_template_id</em>.
+ Setting this value in the CSV file has the same effect as entering a
+ <em>Product ID</em> when creating a new in-app product.
</p>
<p>
- This is equivalent to the Price in the In-app Products UI. The price must be specified in
- micro-units. To convert a currency value to micro-units, you multiply the real value by
- 1,000,000.
- For example, if you want to sell an in-app item for $1.99, you specify <code>1990000</code> in the
- <em>price</em> field.
+ If you specify a <code>Product ID</code> assigned to an in-app product that already
+ exists in a product list, and you've checked the <strong>Overwrite
+ existing products</strong> checkbox in the <em>Import In-app Products</em>
+ dialog, the data for the existing in-app product is overwritten with the
+ values that you specify in the CSV file.
</p>
</dd>
- <dt>pricing_template_id</dt>
+ <dt><code>Publish State</code></dt>
+ <dd>
+ <p>
+ This value must be set to <code>published</code>
+ or <code>unpublished</code>.
+ </p>
+ <p>
+ Setting this value to <code>published</code> has the same effect as
+ navigating to an item's <em>Managed Product Details</em> page and choosing
+ <strong>Active</strong> in the drop-down list next to the in-app product's
+ title and product ID. Setting the value to <code>unpublished</code>
+ has the same effect as choosing <strong>Inactive</strong> in the same
+ drop-down list.
+ </p>
+ </dd>
+ <dt><code>Purchase Type</code></dt>
+ <dd>
+ <p>
+ This value must be set to <code>managed_by_android</code> because batch
+ upload of product lists containing subscriptions is not supported.
+ </p>
+ <p>
+ Setting this value to <code>managed_by_android</code> has the same effect
+ as selecting <strong>Managed product</strong> in the <em>Add New
+ Product</em> dialog when creating an in-app product.
+ </p>
+ </dd>
+ <dt><code>Auto Translate</code></dt>
+ <dd>
+ <p>
+ This value must be set to <code>false</code> because auto-translation of
+ in-app product details isn't supported.
+ </p>
+ <p>
+ If you want to provide translations of an in-app product's title and
+ description, you need to specify these translations explicitly within the
+ <code>Locale</code> value.
+ </p>
+ </dd>
+ <dt><code>Locale</code>, <code>Title</code>, and <code>Description</code></dt>
+ <dd>
+ <p>
+ If you include only one locale for an item, you must specify your app's
+ default locale and the item's default title and description:
+ </p>
+
+<pre class="no-pretty-print">
+<var>app_default_locale</var>; <var>item_default_title</var>; <var>item_default_description</var>;
+</pre>
+
+ <p>
+ Setting these values has the same effect as performing the following
+ sequence of actions:
+ </p>
+ <ol>
+ <li>
+ Choosing a default language when you add a new app to your
+ publisher account.
+ </li>
+ <li>
+ Navigating to an in-app product's <em>Managed Product Details</em> page.
+ </li>
+ <li>
+ Specifying the in-app product's default title and description.
+ </li>
+ </ol>
+ <p>
+ When setting the <code>Locale</code> value, you can use any of the
+ language codes that appear within the <em>Add Your Own Translations</em>
+ dialog. You can access this dialog by navigating to an in-app product's
+ <em>Managed Product Details</em> page and clicking <strong>Add
+ translations</strong> or <strong>Manage translations</strong>.
+ </p>
+ <p class="note">
+ <strong>Note: </strong>When specifying the <code>Title</code> and
+ <code>Description</code> values, use backslashes to escape the semicolon
+ (<code>\;</code>) and backslash (<code>\\</code>) characters.
+ </p>
+ <p>
+ If you want to include translated versions of the item's title and
+ description, you must list the default locale, title, and description,
+ followed by the locales, titles, and descriptions for each translation.
+ In the following example, the in-app product uses <code>en_US</code>
+ (United States English) as the default locale and <code>es_ES</code>
+ (Spain Spanish) as a translation:
+ </p>
+<pre class="no-pretty-print">
+en_US; Invisibility Cloak; Makes you invisible.; es_ES; Capote Invisible; Se vuelven invisible.
+</pre>
+ <p class="note">
+ <strong>Note: </strong>An app contains a single default language, but each
+ in-app product maintains its own list of translations. Therefore, although
+ the first locale in each item's <code>Locale</code> value must be the same
+ throughout the CSV file, the other locales can differ from one item to
+ another.
+ </p>
+ <p>
+ Providing values for multiple translations has the same effect as
+ performing the following sequence of actions:
+ </p>
+ <ol>
+ <li>
+ Navigating to an in-app product's <em>Managed Product Details</em> page.
+ </li>
+ <li>
+ Clicking <strong>Add translations</strong>.
+ </li>
+ <li>
+ Selecting the languages for the translations and clicking
+ <strong>Add</strong>.
+ </li>
+ <li>
+ Choosing one of the languages you added in the previous step.
+ </li>
+ <li>
+ Specifying a new title and description, which serve as translations into
+ the selected language.
+ </li>
+ <li>
+ Repeating steps 4 and 5 to add translations into all other non-default
+ languages.
+ </li>
+ </ol>
+ </dd>
+ <dt><code>Auto Fill Prices</code>, <code>Country</code>, and
+ <code>Price</code></dt>
+ <dd>
+ <p>
+ You can set <code>Auto Fill Prices</code> to <code>true</code> or
+ <code>false</code>.
+ If an in-app product uses a <a href="#pricing-template">pricing
+ template</a>, you should set <code>Auto Fill Prices</code> to
+ <code>false</code>, and you shouldn't set a value for the
+ <code>Price</code>.
+ </p>
+ <p class="note">
+ <strong>Note: </strong>When you specify an item's price in a CSV file, you
+ provide a price in <em>micro-units</em>, where 1,000,000 micro-units is
+ equivalent to 1 unit of real currency.
+ </p>
+ <p>
+ The following sections describe how the value of
+ <code>Auto Fill Prices</code> affects the syntax and meaning of the
+ <code>Country</code> and <code>Price</code> values.
+ </p>
+ <h5>Using auto-filled prices</h5>
+ <p>
+ If you set <code>Auto Fill Prices</code> to <code>true</code>, you specify
+ only the item's default price; you don't include a <code>Country</code>
+ value. Setting <code>Auto Fill Prices</code> to <code>true</code> has the
+ same effect as performing the following sequence of actions:
+ </p>
+ <ol>
+ <li>
+ Navigating to an in-app product's <em>Managed Product Details</em> page.
+ </li>
+ <li>
+ Selecting <strong>Edit</strong> in the <em>Price</em> section.
+ </li>
+ <li>
+ Entering a default, tax-exclusive price. Auto-filled prices include tax.
+ </li>
+ <li>
+ Clicking the checkbox next to <em>COUNTRY</em> in the <em>Edit Local
+ Prices</em> dialog that appears.
+ </li>
+ <li>
+ Selecting <strong>Refresh exchange rates</strong>.
+ </li>
+ <li>
+ Selecting <strong>Apply</strong>.
+ </li>
+ </ol>
+ <p>
+ For example, under the following conditions:
+ </p>
+ <ul>
+ <li>Your app's default locale is <code>en_US</code>.</li>
+ <li>An in-app product's default, tax-exclusive price is $1.99.</li>
+ <li>You want the prices for other countries auto-filled.</li>
+ </ul>
+ <p>
+ ...you'd set the values of <code>Auto Fill Prices</code> and
+ <code>Price</code> at the end of a row in the CSV file as follows:
+ </p>
+
+<pre class="no-pretty-print">
+true,1990000,
+</pre>
+
+ <h5>Not using auto-filled prices</h5>
+ <p>
+ If you set <code>Auto Fill Prices</code> to <code>false</code> instead,
+ you specify a series of <code>Country</code> and <code>Price</code>
+ values for all countries where you distribute your app, including the country corresponding to your app's default locale.
+ Each <code>Country</code> value is the two-letter uppercase <a
+ class="external-link"
+ href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO country
+ code</a> that represents a country where your app is distributed.
+ </p>
+ <p class="note">
+ <strong>Note: </strong>You must provide a country code and price for each
+ country that your app is targeting. To view and edit the list of countries
+ that your app targets, open your app's <em>Pricing & Distribution</em>
+ page.
+ </p>
+ <p>
+ Each <code>Price</code> value represents the cost of the item in
+ micro-units of the currency used in that country. Setting <code>Auto Fill
+ Prices</code> to <code>false</code> has the same effect as performing
+ the following sequence of actions:
+ </p>
+ <ol>
+ <li>
+ Navigating to an in-app product's <em>Managed Product Details</em> page.
+ </li>
+ <li>
+ Selecting <strong>Edit</strong> in the <em>Price</em> section.
+ </li>
+ <li>
+ Explicitly setting tax-inclusive prices for different countries in the
+ <em>Edit Local Prices</em> dialog that appears.
+ </li>
+ <li>
+ Selecting <strong>Apply</strong>.
+ </li>
+ </ol>
+ <p>
+ For example, if you're offering your app for the following prices (all
+ taxes included) in other countries:
+ </p>
+ <ul>
+ <li>R$6.99 in Brazil.</li>
+ <li>129 ₽ in Russia.</li>
+ <li>₹130 in India.</li>
+ <li>Rp 27,000 in Indonesia.</li>
+ <li>$37 in Mexico.</li>
+ </ul>
+ <p>
+ ...you'd set the values of <code>Auto Fill Prices</code>,
+ <code>Country</code>, and <code>Price</code> at the end of a row in the
+ CSV file as follows:
+ </p>
+
+<pre class="no-pretty-print">
+false, BR; 6990000; RU; 129000000; IN; 130000000; ID; 27000000000; MX; 37000000;
+</pre>
+
+ </dd>
+ <dt><code>Pricing Template ID</code></dt>
<dd>
<p>
- If you use this value, you should set <em>autofill</em> to
- <code>false</code> and leave the <em>price</em> column empty.
+ If an item is linked to a pricing template, you should set <code>Auto Fill
+ Prices</code> to <code>false</code>, and you shouldn't set a value for the
+ <code>Price</code> column. If the item isn't linked to a pricing template,
+ you shouldn't set a value for the <code>Pricing Template ID</code>; instead,
+ you should set <code>Auto Fill Prices</code>, <code>Country</code>, and
+ <code>Price</code> based on how you want to set the in-app product's prices.
</p>
<p>
- This value represents the ID of the pricing template that you've linked to
- the in-app product. This ID appears under a pricing template's name
- on the <strong>Pricing template</strong> page. If an in-app product isn't
- linked to a pricing template, its <em>pricing_template_id</em> value is
- empty.
+ Setting this value has the same effect as navigating to an in-app product's
+ <em>Managed Product Details</em> page and linking the product's price to the
+ pricing template that has the same pricing template ID as the one specified
+ in the CSV file. This pricing template ID appears underneath a pricing
+ template's name on the <em>Pricing template</em> page.
</p>
<p>
- If you import a CSV file and choose to overwrite the product list, you can
- update the links between in-app products and pricing templates by changing
- the value of an in-app product's <em>pricing_template_id</em>. Leave the
- value empty to unlink an in-app product from all pricing templates.
+ If you import a CSV file, and you've checked the <strong>Overwrite existing
+ products</strong> checkbox in the <em>Import In-app Products</em> dialog,
+ you can update the links between in-app products and pricing templates. To
+ link the product to a specific pricing template, set the <code>Pricing
+ Template ID</code> value to that pricing template's ID. To unlink an in-app
+ product from all pricing templates, don't set a value for its <code>Pricing
+ Template ID</code>.
</p>
<p>
- <strong>Note: </strong>You can link up to 100 app prices or in-app product
- prices with a particular pricing template. Therefore, don't specify the same
- <em>pricing_template_id</em> value in more than 100 rows of your CSV file.
+ You can link up to 100 app prices or in-app product prices to a particular
+ pricing template. Therefore, don't specify the same <code>Pricing Template
+ ID</code> value in more than 100 rows of a CSV file.
</p>
</dd>
</dl>
-<p class="table-caption" id="language-table"><strong>Table 1.</strong> Language codes you can use
-with the <em>locale</em> field.</p>
-
-<table>
-
-<tr>
-<th>Language</th>
-<th>Code</th>
-<th>Language</th>
-<th>Code</th>
-</tr>
-<tr>
-<td>Chinese</td>
-<td>zh_TW</td>
-<td>Italian</td>
-<td>it_IT</td>
-</tr>
-<tr>
-<td>Czech</td>
-<td>cs_CZ</td>
-<td>Japanese</td>
-<td>ja_JP</td>
-</tr>
-<tr>
-<td>Danish</td>
-<td>da_DK</td>
-<td>Korean</td>
-<td>ko_KR</td>
-</tr>
-<tr>
-<td>Dutch</td>
-<td>nl_NL</td>
-<td>Norwegian</td>
-<td>no_NO</td>
-</tr>
-<tr>
-<td>English</td>
-<td>en_US</td>
-<td>Polish</td>
-<td>pl_PL</td>
-</tr>
-<tr>
-<td>French</td>
-<td>fr_FR</td>
-<td>Portuguese</td>
-<td>pt_PT</td>
-</tr>
-<tr>
-<td>Finnish</td>
-<td>fi_FI</td>
-<td>Russian</td>
-<td>ru_RU</td>
-</tr>
-<tr>
-<td>German</td>
-<td>de_DE</td>
-<td>Spanish</td>
-<td>es_ES</td>
-</tr>
-<tr>
-<td>Hebrew</td>
-<td>iw_IL</td>
-<td>Swedish</td>
-<td>sv_SE</td>
-</tr>
-<tr>
-<td>Hindi</td>
-<td>hi_IN</td>
-<td>--</td>
-<td>--</td>
-</tr>
-</table>
-
<h2 id="pricing-template">
Pricing Templates
</h2>
@@ -466,7 +612,8 @@
can apply to paid apps and in-app products. You can link the prices of up to
100 apps and in-app products to a single pricing template.
</p>
-</p>
+
+<p>
To add a pricing template, do the following:
</p>
@@ -476,14 +623,14 @@
account.
</li>
- <li>In the <strong>Settings</strong> panel, open the <strong>Pricing
- template</strong> page.
+ <li>In the <em>Settings</em> panel, open the <em>Pricing
+ template</em> page.
</li>
<li>
<p>
- If you are adding your first pricing template, the <strong>Add a Pricing
- Template</strong> banner appears. Select <strong>Add template</strong> to
+ If you are adding your first pricing template, the <em>Add a Pricing
+ Template</em> banner appears. Select <strong>Add template</strong> to
create a new template. The new template's <em>Pricing</em> tab appears.
</p>
@@ -544,8 +691,8 @@
account.
</li>
- <li>In the <strong>Settings</strong> panel, open the <strong>Pricing
- template</strong> page. This page shows the list of pricing templates you have
+ <li>In the <em>Settings</em> panel, open the <em>Pricing
+ template</em> page. This page shows the list of pricing templates you have
created for your account.
</li>
@@ -605,8 +752,8 @@
account.
</li>
- <li>In the <strong>All applications</strong> panel, select the app name, then
- open the <strong>In-app Products</strong> page.
+ <li>In the <em>All applications</em> panel, select the app name, then
+ open the <em>In-app Products</em> page.
</li>
<li>Choose the in-app product that you want to link to a pricing template.
@@ -625,7 +772,7 @@
<p>
To link the price of a paid app to a pricing template, you follow a similar
- process on the app's <strong>Pricing & Distribution</strong> page.
+ process on the app's <em>Pricing & Distribution</em> page.
</p>
<h3 id="delete-linked-item">
@@ -657,7 +804,7 @@
<li>Select the app that contains the in-app product you want to delete.
</li>
- <li>Open the app's <strong>In-app Products</strong> page.
+ <li>Open the app's <em>In-app Products</em> page.
</li>
<li>Choose the in-app product that you want to delete.
@@ -731,8 +878,8 @@
account.
</li>
- <li>In the <strong>Settings</strong> panel, open the <strong>Pricing
- template</strong> page, which shows the list of pricing templates you have
+ <li>In the <em>Settings</em> panel, open the <em>Pricing
+ template</em> page, which shows the list of pricing templates you have
created for your account.
</li>
@@ -746,15 +893,15 @@
</li>
</ol>
-<h2 id="billing-purchase-type">Choosing a Product Type</h3>
+<h2 id="billing-purchase-type">Choosing a Product Type</h2>
<p>An item's product type controls how Google Play manages the purchase of the item. The supported
product types include "managed product" and "subscription." Since support for different product
types can vary among versions of the In-app Billing API, make sure that you choose a product
-type that's valid for the version of the In-app Billing API that your app uses. </p>
+type that's valid for the version of the In-app Billing API that your app uses.</p>
<p>For details, refer to the documentation for the <a
-href="{@docRoot}google/play/billing/api.html#producttype">In-app Billing API</a>.
+href="{@docRoot}google/play/billing/api.html#producttype">In-app Billing API</a>.</p>
<h2 id="billing-refunds">Handling Refunds</h2>
@@ -782,9 +929,10 @@
intent.</p>
<p class="note">
- <strong>Note:</strong> Test purchases don't have an <code>orderId</code>
- field. To track test transactions, you use the <code>purchaseToken</code>
- field instead. For more information about working with test purchases, see <a
+ <strong>Note:</strong> When a user completes a test purchase, the
+ <code>orderId</code> field remains blank. To track test transactions, use
+ the <code>purchaseToken</code> field instead. For more information about
+ working with test purchases, see <a
href="{@docRoot}google/play/billing/billing_testing.html">Testing In-app
Billing</a>.
</p>
@@ -799,14 +947,14 @@
<p>For transactions dated 5 December 2012 or later, Google payments assigns a
Merchant Order Number (rather than a Google Order Number) and reports the Merchant
-Order Number as the value of <code>orderId</code>. Here's an
+Order Number as the value of <code>orderID</code>. Here's an
example:</p>
<pre>"orderId" : "GPA.1234-5678-9012-34567"</pre>
<p>For transactions dated previous to 5 December 2012, Google checkout assigned
a Google Order Number and reported that number as the value of
-<code>orderId</code>. Here's an example of an <code>orderId</code> holding a
+<code>orderID</code>. Here's an example of an <code>orderID</code> holding a
Google Order Number:</p>
<pre>"orderId" : "556515565155651"</pre>
@@ -853,8 +1001,8 @@
<p>To locate the key for an app, follow these steps:</p>
<ol>
- <li>Open the <strong>All applications</strong> panel.</li>
- <li>Click on the app name, then open the <strong>Services & APIs</strong>
+ <li>Open the <em>All applications</em> panel.</li>
+ <li>Click on the app name, then open the <em>Services & APIs</em>
page.</li>
<li>Scroll down to the section of the page labeled Your License Key for This
Application, as shown in figure 5.</li>
@@ -869,7 +1017,7 @@
width="700" alt="">
<figcaption>
<b>Figure 5. </b>You can find the license key for each app on the
- <strong>Services & APIs</strong> page.
+ <em>Services & APIs</em> page.
</figcaption>
</figure>
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/_book.yaml b/docs/html/guide/_book.yaml
index 13c948c..f09fe77 100644
--- a/docs/html/guide/_book.yaml
+++ b/docs/html/guide/_book.yaml
@@ -396,6 +396,13 @@
path: /guide/topics/data/data-storage.html
- title: Data Backup
path: /guide/topics/data/backup.html
+ section:
+ - title: Auto Backup
+ path: /guide/topics/data/autobackup.html
+ - title: Key/Value Backup
+ path: /guide/topics/data/keyvaluebackup.html
+ - title: Testing Backup and Restore
+ path: /guide/topics/data/testingbackup.html
- title: App Install Location
path: /guide/topics/data/install-location.html
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/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 9257a76..8fe3f20 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -545,9 +545,16 @@
<li><a href="<?cs var:toroot ?>guide/topics/data/data-storage.html">
<span class="en">Storage Options</span>
</a></li>
- <li><a href="<?cs var:toroot ?>guide/topics/data/backup.html">
+ <li class="nav-section">
+ <div class="nav-section-header"><a href="<?cs var:toroot ?>guide/topics/data/backup.html">
<span class="en">Data Backup</span>
- </a></li>
+ </a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>guide/topics/data/autobackup.html">Auto Backup</a></li>
+ <li><a href="<?cs var:toroot ?>guide/topics/data/keyvaluebackup.html">Key/Value Backup</a></li>
+ <li><a href="<?cs var:toroot ?>guide/topics/data/testingbackup.html">Testing Backup and Restore</a></li>
+ </ul>
+ </li>
<li><a href="<?cs var:toroot ?>guide/topics/data/install-location.html">
<span class="en">App Install Location</span>
</a></li>
diff --git a/docs/html/guide/topics/data/autobackup.jd b/docs/html/guide/topics/data/autobackup.jd
new file mode 100644
index 0000000..3be09d7
--- /dev/null
+++ b/docs/html/guide/topics/data/autobackup.jd
@@ -0,0 +1,257 @@
+page.title=Auto Backup for Apps
+page.tags=backup, marshmallow, androidm
+page.keywords=backup, autobackup
+page.image=images/cards/card-auto-backup_2x.png
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#Files">Files that are backed up</a></li>
+ <li><a href="#BackupLocation">Backup location</a></li>
+ <li><a href="#BackupSchedule">Backup schedule</a></li>
+ <li><a href="#RestoreSchedule">Restore schedule</a></li>
+ <li><a href="#EnablingAutoBackup">Enabling and disabling Auto Backup</a></li>
+ <li><a href="#IncludingFiles">Including and excluding files</a><ul>
+ <li><a href="#XMLSyntax">XML config syntax</a></li>
+ </ul></li>
+ <li><a href="#ImplementingBackupAgent">Implementing BackupAgent</a></li>
+ </ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.app.backup.BackupAgent}</li>
+ <li><a href="{@docRoot}reference/android/R.attr.html">R.attr</a></li>
+ </ol>
+
+</div>
+</div>
+
+Since Android 6.0 (API 23), Android has offered the <em>Auto Backup for Apps</em> feature as
+a way for developers to quickly add backup functionality to their apps. Auto
+Backup preserves app data by uploading it to the user’s Google Drive account.
+The amount of data is limited to 25MB per user of your app and there is no
+charge for storing backup data.
+
+<h2 id="Files">Files that are backed up</h2>
+<p>By default, Auto Backup includes files in most of the directories that are
+assigned to your app by the system:
+<ul>
+ <li>Shared preferences files.
+ <li>Files in the directory returned by {@link android.content.Context#getFilesDir()}.
+ <li>Files in the directory returned by {@link android.content.Context#getDatabasePath(String)},
+ which also includes files created with the
+ {@link android.database.sqlite.SQLiteOpenHelper} class.
+ <li>Files in directories created with {@link android.content.Context#getDir(String,int)}.
+ <li>Files on external storage in the directory returned by
+ {@link android.content.Context#getExternalFilesDir(String)}.</li></ul>
+
+<p>Auto Backup excludes files in directories returned by
+ {@link android.content.Context#getCacheDir()},
+ {@link android.content.Context#getCodeCacheDir()}, or
+ {@link android.content.Context#getNoBackupFilesDir()}. The files saved
+ in these locations are only needed temporarily, or are intentionally
+ excluded from backup operations.
+
+<p>You can configure your app to include and exclude particular files.
+For more information, see the <a href="#IncludingFiles">Include and exclude files</a>
+section.
+
+<h2 id="BackupLocation">Backup location</h2>
+<p>Backup data is stored in a private folder in the user's Google Drive account,
+limited to 25MB per app. The saved data does not count towards the user's
+personal Google Drive quota. Only the most recent backup is stored. When a
+backup is made, the previous backup (if one exists) is deleted.
+
+<p>Users can see a list of apps that have been backed up in the Google Drive app under
+<strong>Settings -> Auto Backup for apps -> Manage backup</strong>. The
+backup data cannot be read by the user or other applications on the device.
+
+<p>Backups from each device-setup-lifetime are stored in separate datasets
+as shown in the following examples:
+<ul>
+ <li>If the user owns two devices, then a backup dataset exists for each device.
+ <li>If the user factory resets a device and then sets up the device with the
+ same account, the backup is stored in a new dataset. Obsolete datasets are
+ automatically deleted after a period of inactivity.</li></ul>
+
+<p class="caution"><strong>Caution:</strong> Once the amount of data reaches
+25MB, the app is banned from sending data to the
+cloud, even if the amount of data later falls under the 25MB threshold. The ban
+affects only the offending device (not other devices that the user owns) and
+lasts for the entire device-setup-lifetime. For example, if the user removes and
+reinstalls the application, the ban is still in effect. The ban is lifted when
+the user performs factory reset on the device.
+
+<h2 id="BackupSchedule">Backup schedule</h2>
+<p>Backups occur automatically when all of the following conditions are met:
+<ul>
+ <li>The user has enabled backup on the device in <strong>Settings</strong> >
+ <strong>Backup & Reset</strong>.
+ <li>At least 24 hours have elapsed since the last backup.
+ <li>The device is idle and charging.
+ <li>The device is connected to a Wi-Fi network. If the device is never connected
+ to a wifi network, then Auto Backup never occurs.</li></ul>
+
+<p>In practice, these conditions occur roughly every night. To conserve network
+bandwidth, upload takes place only if app data has changed.
+
+<p>During Auto Backup, the system shuts down the app to make sure it is no longer
+writing to the file system. By default, the backup system ignores apps that are
+running in the foreground because users would notice their apps being shut down.
+You can override the default behavior by setting the
+<a href="{@docRoot}reference/android/R.attr.html#backupInForeground">backupInForeground</a>
+attribute to true.
+
+<p>To simplify testing, Android includes tools that let you manually initiate
+a backup of your app. For more information, see
+<a href="{@docRoot}guide/topics/data/testingbackup.html">Testing Backup and Restore</a>.
+
+<h2 id="RestoreSchedule">Restore schedule</h2>
+<p>Data is restored whenever the app is installed, either from the Play store,
+during device setup (when the system installs previously installed apps), or
+from running adb install. The restore operation occurs after the APK is
+installed, but before the app is available to be launched by the user.
+
+<p>During the initial device setup wizard, the user is shown a list of available backup
+datasets and is asked which one to restore the data from. Whichever backup
+dataset is selected becomes the ancestral dataset for the device. The device can
+restore from either its own backups or the ancestral dataset. The device
+prioritize its own backup if backups from both sources are available. If the
+user didn't go through the device setup wizard, then the device can restore only from
+its own backups.
+
+<p>To simplify testing, Android includes tools that let you manually initiate
+a restore of your app. For more information, see
+<a href="{@docRoot}guide/topics/data/testingbackup.html">Testing Backup and Restore</a>.
+
+<h2 id="EnablingAutoBackup">Enabling and disabling backup</h2>
+<p>Apps that target Android 6.0 (API level 23) or higher automatically participate
+in Auto Backup. This is because the
+<a href="{@docRoot}reference/android/R.attr.html#allowBackup">android:allowBackup</a>
+attribute, which enables/disables backup, defaults to <code>true</code> if omitted.
+To avoid confusion, we recommend you explicitly set the attribute in the <code><application></code>
+element of your <code>AndroidManifest.xml</code>. For example:
+
+<pre class="prettyprint"><application ...
+ android:allowBackup="true">
+</app></pre>
+
+<p>To disable Auto Backup, add either of the following attributes to the
+application element in your manifest file:
+
+<ul>
+ <li>set <code>android:allowBackup</code> to <code>false</code>. This completely disables data
+ backup. You may want to disable backups when your app can recreate its state
+ through some other mechanism or when your app deals with sensitive
+ information that should not be backed up.</li>
+ <li>set <code>android:allowBackup</code> to <code>true</code> and
+ <code>android:fullBackupOnly</code> to <code>false</code>. With these settings,
+ your app always participates in Key/Value Backup, even when running on devices that
+ support Auto Backup.</li></ul>
+
+<h2 id="IncludingFiles">Including and excluding files</h2>
+<p>By default, the system backs up almost all app data. For more information,
+see <a href="#Files">Files that are backed up</a>. This section shows you how to
+define custom XML rules to control what gets backed up.
+
+<ol>
+ <li>In <code>AndroidManifest.xml</code>, add the <a href="{@docRoot}reference/android/R.attr.html#fullBackupContent">android:fullBackupContent</a> attribute to the
+ <code><application></code> element. This attribute points to an XML file that contains backup
+ rules. For example:
+ <pre class="prettyprint"><application ...
+ android:fullBackupContent="@xml/my_backup_rules">
+ </app></pre></li>
+ <li>Create an XML file called <code>my_backup_rules.xml</code> in the <code>res/xml/</code> directory. Inside the file, add rules with the <code><include></code> and <code><exclude></code> elements. The following sample backs up all shared preferences except <code>device.xml</code>:
+ <pre><?xml version="1.0" encoding="utf-8"?>
+<full-backup-content>
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+</full-backup-content></pre></li>
+
+<h3 id="XMLSyntax">XML Config Syntax</h3>
+<p>The XML syntax for the configuration file is shown below:
+
+<pre class="prettyprint"><full-backup-content>
+ <include domain=["file" | "database" | "sharedpref" | "external" | "root"]
+ path="string" />
+ <exclude domain=["file" | "database" | "sharedpref" | "external" | "root"]
+ path="string" />
+</full-backup-content></pre>
+
+<p>Inside the <code><full-backup-content></code> tag, you can define <code><include></code> and <code><exclude></code>
+elements:
+
+<ul>
+ <li><code><include></code> - Specifies a file or folder to backup. By default, Auto Backup
+includes almost all app files. If you specify an <include> element, the system
+no longer includes any files by default and backs up <em>only the files
+specified</em>. To include multiple files, use multiple <include> elements.
+ <p>note: Files in directories returned by <code>getCacheDir()</code>, <code>getCodeCacheDir()</code>, or
+<code>getNoBackupFilesDir()</code> are always excluded even if you try to include them.</li>
+
+ <li><code><exclude></code> - Specifies a file or folder to exclude during backup. Here are
+some files that are typically excluded from backup: <ul>
+ <li>Files that have device specific identifiers, either issued by a server or
+generated on the device. For example, <a href="https://developers.google.com/cloud-messaging/android/start">Google Cloud Messaging (GCM)</a> needs to
+generate a registration token every time a user installs your app on a new
+device. If the old registration token is restored, the app may behave
+unexpectedly.
+ <li>Account credentials or other sensitive information. Consider asking the
+user to reauthenticate the first time they launch a restored app rather than
+allowing for storage of such information in the backup.
+ <li>Files related to app debugging, such as <a href="{@docRoot}studio/run/index.html#instant-run">instant run files</a>. To exclude instant run files, add the rule <code><exclude
+domain="file" path="instant-run"/></code>
+ <li>Large files that cause the app to exceed the 25MB backup quota.</li> </ul>
+ </li> </ul>
+
+<p class="note"><strong>Note:</strong> If your configuration file specifies both elements, then the
+backup contains everything captured by the <code><include></code> elements minus the
+resources named in the <code><exclude></code> elements. In other words,
+<code><exclude></code> takes precedence.
+
+<p>Each element must include the following two attributes:
+<ul>
+ <li><code>domain</code> - specifies the location of resource. Valid values for this attribute
+include the following: <ul>
+ <li><code>root</code> - the directory on the filesystem where all private files belonging to
+this app are stored.
+ <li><code>file</code> - directories returned by {@link android.content.Context#getFilesDir()}.
+ <li><code>database</code> - directories returned by {@link android.content.Context#getDatabasePath(String) getDatabasePath()}.
+Databases created with {@link android.database.sqlite.SQLiteOpenHelper}
+are stored here.
+ <li><code>sharedpref</code> - the directory where {@link android.content.SharedPreferences}
+are stored.
+ <li><code>external</code> the directory returned by {@link android.content.Context#getExternalFilesDir(String) getExternalFilesDir()}
+ <p>Note: You cannot backup files outside of these locations.</li></ul>
+ <li><code>path</code>: Specifies a file or folder to include in or exclude from backup. Note
+that: <ul>
+ <li>This attribute does not support wildcard or regex syntax.
+ <li>You can use <code>.</code> to reference the current directory, however, you cannot
+reference the parent directory <code>..</code> for security reasons.
+ <li>If you specify a directory, then the rule applies to all files in the
+directory and recursive sub-directories.</li></ul></li></ul>
+
+<h2 id="ImplementingBackupAgent">Implementing BackupAgent</h2>
+<p>Apps that implement Auto Backup do not need to implement {@link android.app.backup.BackupAgent}. However, you can optionally implement a custom {@link android.app.backup.BackupAgent}. Typically, there are two reasons for doing this:
+<ul>
+ <li>You want to receive notification of backup events such as,
+{@link android.app.backup.BackupAgent#onRestoreFinished()} or {@link android.app.backup.BackupAgent#onQuotaExceeded(long,long)}. These callback methods are executed
+even if the app is not running.
+<li>You can't easily express the set of files you want to backup with XML rules.
+In these very rare cases, you can implement a BackupAgent that overrides {@link android.app.backup.BackupAgent#onFullBackup(FullBackupDataOutput)} to
+store what you want. To retain the system's default implementation, call the
+corresponding method on the superclass with <code>super.onFullBackup()</code>.</li></ul>
+
+<p class="note"><strong>Note:</strong> Your <code>BackupAgent</code> must
+implement the abstract methods
+{@link android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor) onBackup()}
+and {@link android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}.
+Those methods are used for Key/Value Backup. So if
+you are not using Key/Value Backup, implement those methods and leave them blank.
+
+<p>For more information, see
+<a href="{@docRoot}guide/topics/data/keyvaluebackup.html#BackupAgent">Extending
+BackupAgent</a>.
\ No newline at end of file
diff --git a/docs/html/guide/topics/data/backup.jd b/docs/html/guide/topics/data/backup.jd
index 619c790..a688c6e 100644
--- a/docs/html/guide/topics/data/backup.jd
+++ b/docs/html/guide/topics/data/backup.jd
@@ -1,930 +1,34 @@
-page.title=Data Backup
+page.title=Backing up App Data to the Cloud
+page.tags=cloud,sync,backup
+
+startpage=true
+
@jd:body
+<p>Users often invest significant time and effort creating data and setting
+preferences within apps. Preserving that data for users if they replace a broken
+device or upgrade to a new one is an important part of ensuring a great user
+experience. This section covers techniques for backing up data to the cloud so
+that users can restore their data.
-<div id="qv-wrapper">
-<div id="qv">
+<p>Android provides two ways for apps to backup their data to the cloud:
+<a href="{@docRoot}guide/topics/data/autobackup.html">Auto Backup for Apps</a> and
+<a href="{@docRoot}guide/topics/data/keyvaluebackup.html">Key/Value Backup</a>.
+Auto Backup, which is available starting API 23, preserves app data by uploading
+it to the user’s Google Drive account. The Key/Value Backup feature (formerly
+known as the Backup API and the Android Backup Service) preserves app data by
+uploading it to the <a href="{@docRoot}google/backup/index.html">Android Backup Service</a>.
- <h2>Quickview</h2>
- <ul>
- <li>Back up the user's data to the cloud in case the user loses it</li>
- <li>If the user upgrades to a new Android-powered device, your app can restore the user's
-data onto the new device</li>
- <li>Easily back up SharedPreferences and private files with BackupAgentHelper</li>
- <li>Requires API Level 8</li>
- </ul>
+<p>Generally, we recommend Auto Backup because it requires no work to implement.
+Apps that target Android 6.0 (API level 23) or higher are automatically enabled
+for Auto Backup. The Auto Backup feature does have some limitations in terms of
+what data it can backup and it's availability on Android 6.0 and higher devices.
+Consider using the Key/Value Backup feature if you have more specific needs for
+backing up your app data. For more information, see <a href="{@docRoot}guide/topics/data/keyvaluebackup.html#Comparison">Comparison of Key/Value and Auto Backup</a></p>
- <h2>In this document</h2>
- <ol>
- <li><a href="#Basics">The Basics</a></li>
- <li><a href="#BackupManifest">Declaring the Backup Agent in Your Manifest</a></li>
- <li><a href="#BackupKey">Registering for Android Backup Service</a></li>
- <li><a href="#BackupAgent">Extending BackupAgent</a>
- <ol>
- <li><a href="#RequiredMethods">Required Methods</a></li>
- <li><a href="#PerformingBackup">Performing backup</a></li>
- <li><a href="#PerformingRestore">Performing restore</a></li>
- </ol>
- </li>
- <li><a href="#BackupAgentHelper">Extending BackupAgentHelper</a>
- <ol>
- <li><a href="#SharedPreferences">Backing up SharedPreferences</a></li>
- <li><a href="#Files">Backing up Private Files</a></li>
- </ol>
- </li>
- <li><a href="#RestoreVersion">Checking the Restore Data Version</a></li>
- <li><a href="#RequestingBackup">Requesting Backup</a></li>
- <li><a href="#RequestingRestore">Requesting Restore</a></li>
- <li><a href="#Testing">Testing Your Backup Agent</a></li>
- </ol>
-
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.app.backup.BackupManager}</li>
- <li>{@link android.app.backup.BackupAgent}</li>
- <li>{@link android.app.backup.BackupAgentHelper}</li>
- </ol>
-
- <h2>See also</h2>
- <ol>
- <li><a href="{@docRoot}tools/help/bmgr.html">{@code bmgr} tool</a></li>
- </ol>
-
-</div>
-</div>
-
-<p>Android's {@link android.app.backup backup} service allows you to copy your persistent
-application data to remote "cloud" storage, in order to provide a restore point for the
-application data and settings. If a user performs a factory reset or converts to a new
-Android-powered device, the system automatically restores your backup data when the application
-is re-installed. This way, your users don't need to reproduce their previous data or
-application settings. This process is completely transparent to the user and does not affect the
-functionality or user experience in your application.</p>
-
-<p>During a backup operation (which your application can request), Android's Backup Manager ({@link
-android.app.backup.BackupManager}) queries your application for backup data, then hands it to
-a backup transport, which then delivers the data to the cloud storage. During a
-restore operation, the Backup Manager retrieves the backup data from the backup transport and
-returns it to your application so your application can restore the data to the device. It's
-possible for your application to request a restore, but that shouldn't be necessary—Android
-automatically performs a restore operation when your application is installed and there exists
-backup data associated with the user. The primary scenario in which backup data is restored is when
-a user resets their device or upgrades to a new device and their previously installed
-applications are re-installed.</p>
-
-<p class="note"><strong>Note:</strong> The backup service is <em>not</em> designed for
-synchronizing application data with other clients or saving data that you'd like to access during
-the normal application lifecycle. You cannot read or write backup data on demand and cannot access
-it in any way other than through the APIs provided by the Backup Manager.</p>
-
-<p>The backup transport is the client-side component of Android's backup framework, which is
-customizable by
-the device manufacturer and service provider. The backup transport may differ from device to device
-and which backup transport is available on any given device is transparent to your application. The
-Backup Manager APIs isolate your application from the actual backup transport available on a given
-device—your application communicates with the Backup Manager through a fixed set of APIs,
-regardless of the underlying transport.</p>
-
-<p>Data backup is <em>not</em> guaranteed to be available on all Android-powered
-devices. However, your application is not adversely affected in the event
-that a device does not provide a backup transport. If you believe that users will benefit from data
-backup in your application, then you can implement it as described in this document, test it, then
-publish your application without any concern about which devices actually perform backup. When your
-application runs on a device that does not provide a backup transport, your application operates
-normally, but will not receive callbacks from the Backup Manager to backup data.</p>
-
-<p>Although you cannot know what the current transport is, you are always assured that your
-backup data cannot be read by other applications on the device. Only the Backup Manager and backup
-transport have access to the data you provide during a backup operation.</p>
-
-<p class="caution"><strong>Caution:</strong> Because the cloud storage and transport service can
-differ from device to device, Android makes no guarantees about the security of your data while
-using backup. You should always be cautious about using backup to store sensitive data, such as
-usernames and passwords.</p>
-
-
-<h2 id="Basics">The Basics</h2>
-
-<p>To backup your application data, you need to implement a backup agent. Your backup
-agent is called by the Backup Manager to provide the data you want to back up. It is also called
-to restore your backup data when the application is re-installed. The Backup Manager handles all
-your data transactions with the cloud storage (using the backup transport) and your backup agent
-handles all your data transactions on the device.</p>
-
-<p>To implement a backup agent, you must:</p>
-
-<ol>
- <li>Declare your backup agent in your manifest file with the <a
-href="{@docRoot}guide/topics/manifest/application-element.html#agent">{@code
-android:backupAgent}</a> attribute.</li>
- <li>Register your application with a backup service. Google offers <a
-href="http://code.google.com/android/backup/index.html">Android Backup Service</a> as a backup
-service for most Android-powered devices, which requires that you register your application in
-order for it to work. Any other backup services available might also require you to register
-in order to store your data on their servers.</li>
- <li>Define a backup agent by either:</p>
- <ol type="a">
- <li><a href="#BackupAgent">Extending BackupAgent</a>
- <p>The {@link android.app.backup.BackupAgent} class provides the central interface with
-which your application communicates with the Backup Manager. If you extend this class
-directly, you must override {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} and {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()} to handle the backup and restore operations for your data.</p>
- <p><em>Or</em></p>
- <li><a href="#BackupAgentHelper">Extending BackupAgentHelper</a>
- <p>The {@link android.app.backup.BackupAgentHelper} class provides a convenient
-wrapper around the {@link android.app.backup.BackupAgent} class, which minimizes the amount of code
-you need to write. In your {@link android.app.backup.BackupAgentHelper}, you must use one or more
-"helper" objects, which automatically backup and restore certain types of data, so that you do not
-need to implement {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} and {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}.</p>
- <p>Android currently provides backup helpers that will backup and restore complete files
-from {@link android.content.SharedPreferences} and <a
-href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>.</p>
- </li>
- </ol>
- </li>
-</ol>
-
-
-
-<h2 id="BackupManifest">Declaring the Backup Agent in Your Manifest</h2>
-
-<p>This is the easiest step, so once you've decided on the class name for your backup agent, declare
-it in your manifest with the <a
-href="{@docRoot}guide/topics/manifest/application-element.html#agent">{@code
-android:backupAgent}</a> attribute in the <a
-href="{@docRoot}guide/topics/manifest/application-element.html">{@code
-<application>}</a> tag.</p>
-
-<p>For example:</p>
-
-<pre>
-<manifest ... >
- ...
- <application android:label="MyApplication"
- <b>android:backupAgent="MyBackupAgent"</b>>
- <activity ... >
- ...
- </activity>
- </application>
-</manifest>
-</pre>
-
-<p>Another attribute you might want to use is <a
-href="{@docRoot}guide/topics/manifest/application-element.html#restoreany">{@code
-android:restoreAnyVersion}</a>. This attribute takes a boolean value to indicate whether you
-want to restore the application data regardless of the current application version compared to the
-version that produced the backup data. (The default value is "{@code false}".) See <a
-href="#RestoreVersion">Checking the Restore Data Version</a> for more information.</p>
-
-<p class="note"><strong>Note:</strong> The backup service and the APIs you must use are
-available only on devices running API Level 8 (Android 2.2) or greater, so you should also
-set your <a
-href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code android:minSdkVersion}</a>
-attribute to "8".</p>
-
-
-
-
-<h2 id="BackupKey">Registering for Android Backup Service</h2>
-
-<p>Google provides a backup transport with <a
-href="http://code.google.com/android/backup/index.html">Android Backup Service</a> for most
-Android-powered devices running Android 2.2 or greater.</p>
-
-<p>In order for your application to perform backup using Android Backup Service, you must
-register your application with the service to receive a Backup Service Key, then
-declare the Backup Service Key in your Android manifest.</p>
-
-<p>To get your Backup Service Key, <a
-href="http://code.google.com/android/backup/signup.html">register for Android Backup Service</a>.
-When you register, you will be provided a Backup Service Key and the appropriate {@code
-<meta-data>} XML code for your Android manifest file, which you must include as a child of the
-{@code <application>} element. For example:</p>
-
-<pre>
-<application android:label="MyApplication"
- android:backupAgent="MyBackupAgent">
- ...
- <meta-data android:name="com.google.android.backup.api_key"
- android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv_4DIsEIyQ" />
-</application>
-</pre>
-
-<p>The <code>android:name</code> must be <code>"com.google.android.backup.api_key"</code> and
-the <code>android:value</code> must be the Backup Service Key received from the Android Backup
-Service registration.</p>
-
-<p>If you have multiple applications, you must register each one, using the respective package
-name.</p>
-
-<p class="note"><strong>Note:</strong> The backup transport provided by Android Backup Service is
-not guaranteed to be available
-on all Android-powered devices that support backup. Some devices might support backup
-using a different transport, some devices might not support backup at all, and there is no way for
-your application to know what transport is used on the device. However, if you implement backup for
-your application, you should always include a Backup Service Key for Android Backup Service so
-your application can perform backup when the device uses the Android Backup Service transport. If
-the device does not use Android Backup Service, then the {@code <meta-data>} element with the
-Backup Service Key is ignored.</p>
-
-
-
-
-<h2 id="BackupAgent">Extending BackupAgent</h2>
-
-<p>Most applications shouldn't need to extend the {@link android.app.backup.BackupAgent} class
-directly, but should instead <a href="#BackupAgentHelper">extend BackupAgentHelper</a> to take
-advantage of the built-in helper classes that automatically backup and restore your files. However,
-you might want to extend {@link android.app.backup.BackupAgent} directly if you need to:</p>
-<ul>
- <li>Version your data format. For instance, if you anticipate the need to revise the
-format in which you write your application data, you can build a backup agent to cross-check your
-application version during a restore operation and perform any necessary compatibility work if the
-version on the device is different than that of the backup data. For more information, see <a
-href="#RestoreVersion">Checking the Restore Data Version</a>.</li>
- <li>Instead of backing up an entire file, you can specify the portions of data the should be
-backed up and how each portion is then restored to the device. (This can also help you manage
-different versions, because you read and write your data as unique entities, rather than
-complete files.)</li>
- <li>Back up data in a database. If you have an SQLite database that you want to restore when
-the user re-installs your application, you need to build a custom {@link
-android.app.backup.BackupAgent} that reads the appropriate data during a backup operation, then
-create your table and insert the data during a restore operation.</li>
-</ul>
-
-<p>If you don't need to perform any of the tasks above and want to back up complete files from
-{@link android.content.SharedPreferences} or <a
-href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>, you
-should skip to <a href="#BackupAgentHelper">Extending BackupAgentHelper</a>.</p>
-
-
-
-<h3 id="RequiredMethods">Required Methods</h3>
-
-<p>When you create a backup agent by extending {@link android.app.backup.BackupAgent}, you
-must implement the following callback methods:</p>
-
-<dl>
- <dt>{@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()}</dt>
- <dd>The Backup Manager calls this method after you <a href="#RequestingBackup">request a
-backup</a>. In this method, you read your application data from the device and pass the data you
-want to back up to the Backup Manager, as described below in <a href="#PerformingBackup">Performing
-backup</a>.</dd>
-
- <dt>{@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}</dt>
- <dd>The Backup Manager calls this method during a restore operation (you can <a
-href="#RequestingRestore">request a restore</a>, but the system automatically performs restore when
-the user re-installs your application). When it calls this method, the Backup Manager delivers your
-backup data, which you then restore to the device, as described below in <a
-href="#PerformingRestore">Performing restore</a>.</dd>
-</dl>
-
-
-
-<h3 id="PerformingBackup">Performing backup</h3>
-
-
-<p>When it's time to back up your application data, the Backup Manager calls your {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method. This is where you must provide your application data to the Backup Manager so
-it can be saved to cloud storage.</p>
-
-<p>Only the Backup Manager can call your backup agent's {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method. Each time that your application data changes and you want to perform a backup,
-you must request a backup operation by calling {@link
-android.app.backup.BackupManager#dataChanged()} (see <a href="#RequestingBackup">Requesting
-Backup</a> for more information). A backup request does not result in an immediate call to your
-{@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method. Instead, the Backup Manager waits for an appropriate time, then performs
-backup for all applications that have requested a backup since the last backup was performed.</p>
-
-<p class="note"><strong>Tip:</strong> While developing your application, you can initiate an
-immediate backup operation from the Backup Manager with the <a
-href="{@docRoot}tools/help/bmgr.html">{@code bmgr} tool</a>.</p>
-
-<p>When the Backup Manager calls your {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method, it passes three parameters:</p>
-
-<dl>
- <dt>{@code oldState}</dt>
- <dd>An open, read-only {@link android.os.ParcelFileDescriptor} pointing to the last backup
-state provided by your application. This is not the backup data from cloud storage, but a
-local representation of the data that was backed up the last time {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} was called (as defined by {@code newState}, below, or from {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}—more about this in the next section). Because {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} does not allow you to read existing backup data in
-the cloud storage, you can use this local representation to determine whether your data has changed
-since the last backup.</dd>
- <dt>{@code data}</dt>
- <dd>A {@link android.app.backup.BackupDataOutput} object, which you use to deliver your backup
-data to the Backup Manager.</dd>
- <dt>{@code newState}</dt>
- <dd>An open, read/write {@link android.os.ParcelFileDescriptor} pointing to a file in which
-you must write a representation of the data that you delivered to {@code data} (a representation
-can be as simple as the last-modified timestamp for your file). This object is
-returned as {@code oldState} the next time the Backup Manager calls your {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method. If you do not write your backup data to {@code newState}, then {@code oldState}
-will point to an empty file next time Backup Manager calls {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()}.</dd>
-</dl>
-
-<p>Using these parameters, you should implement your {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method to do the following:</p>
-
-<ol>
- <li>Check whether your data has changed since the last backup by comparing {@code oldState} to
-your current data. How you read data in {@code oldState} depends on how you originally wrote it to
-{@code newState} (see step 3). The easiest way to record the state of a file is with its
-last-modified timestamp. For example, here's how you can read and compare a timestamp from {@code
-oldState}:
- <pre>
-// Get the oldState input stream
-FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
-DataInputStream in = new DataInputStream(instream);
-
-try {
- // Get the last modified timestamp from the state file and data file
- long stateModified = in.readLong();
- long fileModified = mDataFile.lastModified();
-
- if (stateModified != fileModified) {
- // The file has been modified, so do a backup
- // Or the time on the device changed, so be safe and do a backup
- } else {
- // Don't back up because the file hasn't changed
- return;
- }
-} catch (IOException e) {
- // Unable to read state file... be safe and do a backup
-}
-</pre>
- <p>If nothing has changed and you don't need to back up, skip to step 3.</p>
- </li>
- <li>If your data has changed, compared to {@code oldState}, write the current data to
-{@code data} to back it up to the cloud storage.
- <p>You must write each chunk of data as an "entity" in the {@link
-android.app.backup.BackupDataOutput}. An entity is a flattened binary data
-record that is identified by a unique key string. Thus, the data set that you back up is
-conceptually a set of key-value pairs.</p>
- <p>To add an entity to your backup data set, you must:</p>
- <ol>
- <li>Call {@link android.app.backup.BackupDataOutput#writeEntityHeader(String,int)
-writeEntityHeader()}, passing a unique string key for the data you're about to write and the data
-size.</li>
- <li>Call {@link android.app.backup.BackupDataOutput#writeEntityData(byte[],int)
-writeEntityData()}, passing a byte buffer that contains your data and the number of bytes to write
-from the buffer (which should match the size passed to {@link
-android.app.backup.BackupDataOutput#writeEntityHeader(String,int) writeEntityHeader()}).</li>
- </ol>
- <p>For example, the following code flattens some data into a byte stream and writes it into a
-single entity:</p>
- <pre>
-// Create buffer stream and data output stream for our data
-ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
-DataOutputStream outWriter = new DataOutputStream(bufStream);
-// Write structured data
-outWriter.writeUTF(mPlayerName);
-outWriter.writeInt(mPlayerScore);
-// Send the data to the Backup Manager via the BackupDataOutput
-byte[] buffer = bufStream.toByteArray();
-int len = buffer.length;
-data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
-data.writeEntityData(buffer, len);
-</pre>
- <p>Perform this for each piece of data that you want to back up. How you divide your data into
-entities is up to you (and you might use just one entity).</p>
- </li>
- <li>Whether or not you perform a backup (in step 2), write a representation of the current data to
-the {@code newState} {@link android.os.ParcelFileDescriptor}. The Backup Manager retains this object
-locally as a representation of the data that is currently backed up. It passes this back to you as
-{@code oldState} the next time it calls {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} so you can determine whether another backup is necessary (as handled in step 1). If you
-do not write the current data state to this file, then
-{@code oldState} will be empty during the next callback.
- <p>The following example saves a representation of the current data into {@code newState} using
-the file's last-modified timestamp:</p>
- <pre>
-FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
-DataOutputStream out = new DataOutputStream(outstream);
-
-long modified = mDataFile.lastModified();
-out.writeLong(modified);
-</pre>
- </li>
-</ol>
-
-<p class="caution"><strong>Caution:</strong> If your application data is saved to a file, make sure
-that you use synchronized statements while accessing the file so that your backup agent does not
-read the file while an Activity in your application is also writing the file.</p>
-
-
-
-
-<h3 id="PerformingRestore">Performing restore</h3>
-
-<p>When it's time to restore your application data, the Backup Manager calls your backup
-agent's {@link android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()} method. When it calls this method, the Backup Manager delivers your backup data so
-you can restore it onto the device.</p>
-
-<p>Only the Backup Manager can call {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}, which happens automatically when the system installs your application and
-finds existing backup data. However, you can request a restore operation for
-your application by calling {@link
-android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()} (see <a
-href="#RequestingRestore">Requesting restore</a> for more information).</p>
-
-<p class="note"><strong>Note:</strong> While developing your application, you can also request a
-restore operation with the <a href="{@docRoot}tools/help/bmgr.html">{@code bmgr}
-tool</a>.</p>
-
-<p>When the Backup Manager calls your {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()} method, it passes three parameters:</p>
-
-<dl>
- <dt>{@code data}</dt>
- <dd>A {@link android.app.backup.BackupDataInput}, which allows you to read your backup
-data.</dd>
- <dt>{@code appVersionCode}</dt>
- <dd>An integer representing the value of your application's <a
-href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
-manifest attribute, as it was when this data was backed up. You can use this to cross-check the
-current application version and determine if the data format is compatible. For more
-information about using this to handle different versions of restore data, see the section
-below about <a href="#RestoreVersion">Checking the Restore Data Version</a>.</dd>
- <dt>{@code newState}</dt>
- <dd>An open, read/write {@link android.os.ParcelFileDescriptor} pointing to a file in which
-you must write the final backup state that was provided with {@code data}. This object is
-returned as {@code oldState} the next time {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} is called. Recall that you must also write the same {@code newState} object in the
-{@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} callback—also doing it here ensures that the {@code oldState} object given to
-{@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} is valid even the first time {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} is called after the device is restored.</dd>
-</dl>
-
-<p>In your implementation of {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}, you should call {@link android.app.backup.BackupDataInput#readNextHeader()} on the
-{@code data} to iterate
-through all entities in the data set. For each entity found, do the following:</p>
-
-<ol>
- <li>Get the entity key with {@link android.app.backup.BackupDataInput#getKey()}.</li>
- <li>Compare the entity key to a list of known key values that you should have declared as static
-final strings inside your {@link android.app.backup.BackupAgent} class. When the key matches one of
-your known key strings, enter into a statement to extract the entity data and save it to the device:
- <ol>
- <li>Get the entity data size with {@link
-android.app.backup.BackupDataInput#getDataSize()} and create a byte array of that size.</li>
- <li>Call {@link android.app.backup.BackupDataInput#readEntityData(byte[],int,int)
-readEntityData()} and pass it the byte array, which is where the data will go, and specify the
-start offset and the size to read.</li>
- <li>Your byte array is now full and you can read the data and write it to the device
-however you like.</li>
- </ol>
- </li>
- <li>After you read and write your data back to the device, write the state of your data to the
-{@code newState} parameter the same as you do during {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()}.
-</ol>
-
-<p>For example, here's how you can restore the data backed up by the example in the previous
-section:</p>
-
-<pre>
-@Override
-public void onRestore(BackupDataInput data, int appVersionCode,
- ParcelFileDescriptor newState) throws IOException {
- // There should be only one entity, but the safest
- // way to consume it is using a while loop
- while (data.readNextHeader()) {
- String key = data.getKey();
- int dataSize = data.getDataSize();
-
- // If the key is ours (for saving top score). Note this key was used when
- // we wrote the backup entity header
- if (TOPSCORE_BACKUP_KEY.equals(key)) {
- // Create an input stream for the BackupDataInput
- byte[] dataBuf = new byte[dataSize];
- data.readEntityData(dataBuf, 0, dataSize);
- ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
- DataInputStream in = new DataInputStream(baStream);
-
- // Read the player name and score from the backup data
- mPlayerName = in.readUTF();
- mPlayerScore = in.readInt();
-
- // Record the score on the device (to a file or something)
- recordScore(mPlayerName, mPlayerScore);
- } else {
- // We don't know this entity key. Skip it. (Shouldn't happen.)
- data.skipEntityData();
- }
- }
-
- // Finally, write to the state blob (newState) that describes the restored data
- FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
- DataOutputStream out = new DataOutputStream(outstream);
- out.writeUTF(mPlayerName);
- out.writeInt(mPlayerScore);
-}
-</pre>
-
-<p>In this example, the {@code appVersionCode} parameter passed to {@link
-android.app.backup.BackupAgent#onRestore onRestore()} is not used. However, you might want to use
-it if you've chosen to perform backup when the user's version of the application has actually moved
-backward (for example, the user went from version 1.5 of your app to 1.0). For more information, see
-the section about <a href="#RestoreVersion">Checking the Restore Data Version</a>.</p>
-
-<div class="special">
-<p>For an example implementation of {@link android.app.backup.BackupAgent}, see the <a
-href="{@docRoot}resources/samples/BackupRestore/src/com/example/android/backuprestore/ExampleAgent.html">{@code
-ExampleAgent}</a> class in the <a
-href="{@docRoot}resources/samples/BackupRestore/index.html">Backup and Restore</a> sample
-application.</p>
-</div>
-
-
-
-
-
-
-<h2 id="BackupAgentHelper">Extending BackupAgentHelper</h2>
-
-<p>You should build your backup agent using {@link android.app.backup.BackupAgentHelper} if you want
-to back up complete files (from either {@link android.content.SharedPreferences} or <a
-href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>).
-Building your backup agent with {@link android.app.backup.BackupAgentHelper} requires far less
-code than extending {@link android.app.backup.BackupAgent}, because you don't have to implement
-{@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} and {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}.</p>
-
-<p>Your implementation of {@link android.app.backup.BackupAgentHelper} must
-use one or more backup helpers. A backup helper is a specialized
-component that {@link android.app.backup.BackupAgentHelper} summons to perform backup and
-restore operations for a particular type of data. The Android framework currently provides two
-different helpers:</p>
-<ul>
- <li>{@link android.app.backup.SharedPreferencesBackupHelper} to backup {@link
-android.content.SharedPreferences} files.</li>
- <li>{@link android.app.backup.FileBackupHelper} to backup files from <a
-href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>.</li>
-</ul>
-
-<p>You can include multiple helpers in your {@link android.app.backup.BackupAgentHelper}, but only
-one helper is needed for each data type. That is, if you have multiple {@link
-android.content.SharedPreferences} files, then you need only one {@link
-android.app.backup.SharedPreferencesBackupHelper}.</p>
-
-<p>For each helper you want to add to your {@link android.app.backup.BackupAgentHelper}, you must do
-the following during your {@link android.app.backup.BackupAgent#onCreate()} method:</p>
-<ol>
- <li>Instantiate in instance of the desired helper class. In the class constructor, you must
-specify the appropriate file(s) you want to backup.</li>
- <li>Call {@link android.app.backup.BackupAgentHelper#addHelper(String,BackupHelper) addHelper()}
-to add the helper to your {@link android.app.backup.BackupAgentHelper}.</li>
-</ol>
-
-<p>The following sections describe how to create a backup agent using each of the available
-helpers.</p>
-
-
-
-<h3 id="SharedPreferences">Backing up SharedPreferences</h3>
-
-<p>When you instantiate a {@link android.app.backup.SharedPreferencesBackupHelper}, you must
-include the name of one or more {@link android.content.SharedPreferences} files.</p>
-
-<p>For example, to back up a {@link android.content.SharedPreferences} file named
-"user_preferences", a complete backup agent using {@link android.app.backup.BackupAgentHelper} looks
-like this:</p>
-
-<pre>
-public class MyPrefsBackupAgent extends BackupAgentHelper {
- // The name of the SharedPreferences file
- static final String PREFS = "user_preferences";
-
- // A key to uniquely identify the set of backup data
- static final String PREFS_BACKUP_KEY = "prefs";
-
- // Allocate a helper and add it to the backup agent
- @Override
- public void onCreate() {
- SharedPreferencesBackupHelper helper =
- new SharedPreferencesBackupHelper(this, PREFS);
- addHelper(PREFS_BACKUP_KEY, helper);
- }
-}
-</pre>
-
-<p>That's it! That's your entire backup agent. The {@link
-android.app.backup.SharedPreferencesBackupHelper} includes all the code
-needed to backup and restore a {@link android.content.SharedPreferences} file.</p>
-
-<p>When the Backup Manager calls {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} and {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()}, {@link android.app.backup.BackupAgentHelper} calls your backup helpers to perform
-backup and restore for your specified files.</p>
-
-<p class="note"><strong>Note:</strong> {@link android.content.SharedPreferences} are threadsafe, so
-you can safely read and write the shared preferences file from your backup agent and
-other activities.</p>
-
-
-
-<h3 id="Files">Backing up other files</h3>
-
-<p>When you instantiate a {@link android.app.backup.FileBackupHelper}, you must include the name of
-one or more files that are saved to your application's <a
-href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>
-(as specified by {@link android.content.ContextWrapper#getFilesDir()}, which is the same
-location where {@link android.content.Context#openFileOutput(String,int) openFileOutput()} writes
-files).</p>
-
-<p>For example, to backup two files named "scores" and "stats," a backup agent using {@link
-android.app.backup.BackupAgentHelper} looks like this:</p>
-
-<pre>
-public class MyFileBackupAgent extends BackupAgentHelper {
- // The name of the file
- static final String TOP_SCORES = "scores";
- static final String PLAYER_STATS = "stats";
-
- // A key to uniquely identify the set of backup data
- static final String FILES_BACKUP_KEY = "myfiles";
-
- // Allocate a helper and add it to the backup agent
- @Override
- public void onCreate() {
- FileBackupHelper helper = new FileBackupHelper(this,
- TOP_SCORES, PLAYER_STATS);
- addHelper(FILES_BACKUP_KEY, helper);
- }
-}
-</pre>
-
-<p>The {@link android.app.backup.FileBackupHelper} includes all the code necessary to backup and
-restore files that are saved to your application's <a
-href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>..</p>
-
-<p>However, reading and writing to files on internal storage is <strong>not threadsafe</strong>. To
-ensure that your backup agent does not read or write your files at the same time as your activities,
-you must use synchronized statements each time you perform a read or write. For example,
-in any Activity where you read and write the file, you need an object to use as the intrinsic
-lock for the synchronized statements:</p>
-
-<pre>
-// Object for intrinsic lock
-static final Object sDataLock = new Object();
-</pre>
-
-<p>Then create a synchronized statement with this lock each time you read or write the files. For
-example, here's a synchronized statement for writing the latest score in a game to a file:</p>
-
-<pre>
-try {
- synchronized (MyActivity.sDataLock) {
- File dataFile = new File({@link android.content.Context#getFilesDir()}, TOP_SCORES);
- RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
- raFile.writeInt(score);
- }
-} catch (IOException e) {
- Log.e(TAG, "Unable to write to file");
-}
-</pre>
-
-<p>You should synchronize your read statements with the same lock.</p>
-
-<p>Then, in your {@link android.app.backup.BackupAgentHelper}, you must override {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} and {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()} to synchronize the backup and restore operations with the same
-intrinsic lock. For example, the {@code MyFileBackupAgent} example from above needs the following
-methods:</p>
-
-<pre>
-@Override
-public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) throws IOException {
- // Hold the lock while the FileBackupHelper performs backup
- synchronized (MyActivity.sDataLock) {
- super.onBackup(oldState, data, newState);
- }
-}
-
-@Override
-public void onRestore(BackupDataInput data, int appVersionCode,
- ParcelFileDescriptor newState) throws IOException {
- // Hold the lock while the FileBackupHelper restores the file
- synchronized (MyActivity.sDataLock) {
- super.onRestore(data, appVersionCode, newState);
- }
-}
-</pre>
-
-<p>That's it. All you need to do is add your {@link android.app.backup.FileBackupHelper} in the
-{@link android.app.backup.BackupAgent#onCreate()} method and override {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} and {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
-onRestore()} to synchronize read and write operations.</p>
-
-<div class="special">
-<p>For an example implementation of {@link
-android.app.backup.BackupAgentHelper} with {@link android.app.backup.FileBackupHelper}, see the
-{@code FileHelperExampleAgent} class in the <a
-href="{@docRoot}resources/samples/BackupRestore/index.html">Backup and Restore</a> sample
-application.</p>
-</div>
-
-
-
-
-
-
-<h2 id="RestoreVersion">Checking the Restore Data Version</h2>
-
-<p>When the Backup Manager saves your data to cloud storage, it automatically includes the version
-of your application, as defined by your manifest file's <a
-href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
-attribute. Before the Backup Manager calls your backup agent to restore your data, it
-looks at the <a
-href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
-android:versionCode}</a> of the installed application and compares it to the value
-recorded in the restore data set. If the version recorded in the restore data set is
-<em>newer</em> than the application version on the device, then the user has downgraded their
-application. In this case, the Backup Manager will abort the restore operation for your application
-and not call your {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
-method, because the restore set is considered meaningless to an older version.</p>
-
-<p>You can override this behavior with the <a
-href="{@docRoot}guide/topics/manifest/application-element.html#restoreany">{@code
-android:restoreAnyVersion}</a> attribute. This attribute is either "{@code true}" or "{@code
-false}" to indicate whether you want to restore the application regardless of the restore set
-version. The default value is "{@code false}". If you define this to be "{@code true}" then the
-Backup Manager will ignore the <a
-href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
-and call your {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
-method in all cases. In doing so, you can manually check for the version difference in your {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
-method and take any steps necessary to make the data compatible if the versions conflict.</p>
-
-<p>To help you handle different versions during a restore operation, the {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
-method passes you the version code included with the restore data set as the {@code appVersionCode}
-parameter. You can then query the current application's version code with the {@link
-android.content.pm.PackageInfo#versionCode PackageInfo.versionCode} field. For example:</p>
-
-<pre>
-PackageInfo info;
-try {
- String name = {@link android.content.ContextWrapper#getPackageName() getPackageName}();
- info = {@link android.content.ContextWrapper#getPackageManager
-getPackageManager}().{@link android.content.pm.PackageManager#getPackageInfo(String,int)
-getPackageInfo}(name,0);
-} catch (NameNotFoundException nnfe) {
- info = null;
-}
-
-int version;
-if (info != null) {
- version = info.versionCode;
-}
-</pre>
-
-<p>Then simply compare the {@code version} acquired from {@link android.content.pm.PackageInfo}
-to the {@code appVersionCode} passed into {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}.
-</p>
-
-<p class="caution"><strong>Caution:</strong> Be certain you understand the consequences of setting
-<a href="{@docRoot}guide/topics/manifest/application-element.html#restoreany">{@code
-android:restoreAnyVersion}</a> to "{@code true}" for your application. If each version of your
-application that supports backup does not properly account for variations in your data format during
-{@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()},
-then the data on the device could be saved in a format incompatible with the version currently
-installed on the device.</p>
-
-
-
-<h2 id="RequestingBackup">Requesting Backup</h2>
-
-<p>You can request a backup operation at any time by calling {@link
-android.app.backup.BackupManager#dataChanged()}. This method notifies the Backup Manager that you'd
-like to backup your data using your backup agent. The Backup Manager then calls your backup
-agent's {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()} method at an opportune time in the future. Typically, you should
-request a backup each time your data changes (such as when the user changes an application
-preference that you'd like to back up). If you call {@link
-android.app.backup.BackupManager#dataChanged()} several times consecutively, before the Backup
-Manager requests a backup from your agent, your agent still receives just one call to {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
-onBackup()}.</p>
-
-<p class="note"><strong>Note:</strong> While developing your application, you can request a
-backup and initiate an immediate backup operation with the <a
-href="{@docRoot}tools/help/bmgr.html">{@code bmgr}
-tool</a>.</p>
-
-
-<h2 id="RequestingRestore">Requesting Restore</h2>
-
-<p>During the normal life of your application, you shouldn't need to request a restore operation.
-They system automatically checks for backup data and performs a restore when your application is
-installed. However, you can manually request a restore operation by calling {@link
-android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()}, if necessary. In
-which case, the Backup Manager calls your {@link
-android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
-implementation, passing the data from the current set of backup data.</p>
-
-<p class="note"><strong>Note:</strong> While developing your application, you can request a
-restore operation with the <a href="{@docRoot}tools/help/bmgr.html">{@code bmgr}
-tool</a>.</p>
-
-
-<h2 id="Testing">Testing Your Backup Agent</h2>
-
-<p>Once you've implemented your backup agent, you can test the backup and restore functionality
-with the following procedure, using <a
-href="{@docRoot}tools/help/bmgr.html">{@code bmgr}</a>.</p>
-
-<ol>
- <li>Install your application on a suitable Android system image
- <ul>
- <li>If using the emulator, create and use an AVD with Android 2.2 (API Level 8).</li>
- <li>If using a device, the device must be running Android 2.2 or greater and have Google
-Play built in.</li>
- </ul>
- </li>
- <li>Ensure that backup is enabled
- <ul>
- <li>If using the emulator, you can enable backup with the following command from your SDK
-{@code tools/} path:
-<pre class="no-pretty-print">adb shell bmgr enable true</pre>
- </li>
- <li>If using a device, open the system <b>Settings</b>, select
- <b>Backup & reset</b>, then enable
- <b>Back up my data</b> and <b>Automatic restore</b>.</li>
- </ul>
- </li>
- <li>Open your application and initialize some data
- <p>If you've properly implemented backup in your application, then it should request a
-backup each time the data changes. For example, each time the user changes some data, your app
-should call {@link android.app.backup.BackupManager#dataChanged()}, which adds a backup request to
-the Backup Manager queue. For testing purposes, you can also make a request with the following
-{@code bmgr} command:</p>
-<pre class="no-pretty-print">adb shell bmgr backup <em>your.package.name</em></pre>
- </li>
- <li>Initiate a backup operation:
-<pre class="no-pretty-print">adb shell bmgr run</pre>
- <p>This forces the Backup Manager to perform all backup requests that are in its
-queue.</p>
- <li>Uninstall your application:
-<pre class="no-pretty-print">adb uninstall <em>your.package.name</em></pre>
- </li>
- <li>Re-install your application.</li>
-</ol>
-
-<p>If your backup agent is successful, all the data you initialized in step 4 is restored.</p>
-
-
+<p class="note"><strong>Note:</strong> These data backup features are not designed for synchronizing app data with other clients or
+saving data that you'd like to access during the normal application lifecycle.
+You cannot read or write backup data on demand. For synchronizing app data, see
+<a href="{@docRoot}training/sync-adapters/index.html">Transferring
+Data Using Sync Adapters</a> or <a href="https://developers.google.com/drive/android/">Google Drive Android
+API</a>.
\ No newline at end of file
diff --git a/docs/html/guide/topics/data/images/backup-framework.png b/docs/html/guide/topics/data/images/backup-framework.png
new file mode 100644
index 0000000..2ba2e612
--- /dev/null
+++ b/docs/html/guide/topics/data/images/backup-framework.png
Binary files differ
diff --git a/docs/html/guide/topics/data/index.jd b/docs/html/guide/topics/data/index.jd
index 3872825..2365f18 100644
--- a/docs/html/guide/topics/data/index.jd
+++ b/docs/html/guide/topics/data/index.jd
@@ -5,21 +5,3 @@
@jd:body
-<div class="landing-docs">
-
-
- <div class="col-12">
- <h3>Training</h3>
-
- <a href="{@docRoot}training/backup/index.html">
- <h4>Backing up App Data to the Cloud</h4>
- <p>
- This class covers techniques for backing up data to the cloud so that
- users can restore their data when recovering from a data loss (such as a
- factory reset) or installing your application on a new device.
- </p>
- </a>
-
- </div>
-
-</div>
diff --git a/docs/html/guide/topics/data/keyvaluebackup.jd b/docs/html/guide/topics/data/keyvaluebackup.jd
new file mode 100644
index 0000000..c7c5e2f
--- /dev/null
+++ b/docs/html/guide/topics/data/keyvaluebackup.jd
@@ -0,0 +1,884 @@
+page.title=Key/Value Backup
+page.tags=backup, marshmallow, androidm
+page.keywords=backup, kvbackup
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#Comparison">Comparison to Auto Backup</a></li>
+ <li><a href="#ImplementingBackup">Implementing Key/Value Backup</a></li>
+
+ <li><a href="#BackupManifest">Declaring the backup agent in your manifest</a></li>
+ <li><a href="#BackupKey">Registering for Android Backup Service</a></li>
+ <li><a href="#BackupAgent">Extending BackupAgent</a>
+ <ol>
+ <li><a href="#RequiredMethods">Required methods</a></li>
+ <li><a href="#PerformingBackup">Performing backup</a></li>
+ <li><a href="#PerformingRestore">Performing restore</a></li>
+ </ol>
+ </li>
+ <li><a href="#BackupAgentHelper">Extending BackupAgentHelper</a>
+ <ol>
+ <li><a href="#SharedPreferences">Backing up SharedPreferences</a></li>
+ <li><a href="#Files">Backing up private files</a></li>
+ </ol>
+ </li>
+ <li><a href="#RestoreVersion">Checking the restore data version</a></li>
+ <li><a href="#RequestingBackup">Requesting backup</a></li>
+ <li><a href="#RequestingRestore">Requesting restore</a></li>
+ <li><a href="#Migrating">Migrating to Auto Backup</a></li>
+ </ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.app.backup.BackupManager}</li>
+ <li>{@link android.app.backup.BackupAgent}</li>
+ <li>{@link android.app.backup.BackupAgentHelper}</li>
+ </ol>
+
+</div>
+</div>
+
+
+<p>Since Android 2.2 (API 8), Android has offered the <em>Key/Value Backup</em>
+feature as a way for developers to backup app data to the cloud. The Key/Value
+Backup feature (formerly known as the Backup API and the Android Backup Service)
+preserves app data by uploading it to
+<a href="{@docRoot}google/backup/index.html">Android Backup Service</a>.
+The amount of data is limited to 5MB per user of your app and there is
+no charge for storing backup data.
+
+<p class="note"><strong>Note:</strong> If your app implements Key/Value Backup
+and targets API 23 or higher, you should set
+<a href="{@docRoot}reference/android/R.attr.html#fullBackupOnly">android:fullBackupOnly</a>.
+This attribute indicates whether or not to use Auto Backup on devices where it is available.
+
+<h2 id="Comparison">Comparison to Auto Backup</h2>
+<p>Like Auto Backup, Key/Value Backups are restored automatically whenever the app
+is installed. The following table describes some of the key differences between Key/Value Backup and Auto Backup:
+
+<table>
+ <tr>
+ <th>Key/Value Backup</th>
+ <th>Auto Backup</th>
+ </tr>
+ <tr>
+ <td>Available in API 8, Android 2.2</td>
+ <td>Available in API 23, Android 6.0</td>
+ </tr>
+ <tr>
+ <td>Apps must implement a {@link android.app.backup.BackupAgent}. The backup agent defines what data to backup and how to
+restore data.</td>
+ <td>By default, Auto Backup includes almost all of the app's files. You can
+use XML to include and exclude files. Under the hood, Auto Backup relies on a
+backup agent that is built into the framework.</td>
+ </tr>
+ <tr>
+ <td>Apps must issue a request when there is data
+that is ready to be backed up. Requests
+from multiple apps are batched and executed every few hours.</td>
+ <td>Backups happen automatically roughly once a day.</td>
+ </tr>
+ <tr>
+ <td>Backup data can be transmitted via wifi or cellular data.</td>
+ <td>Backup data is tranmitted only via wifi. If the device is never connected to a
+wifi network, then Auto Backup never occurs.</td>
+ </tr>
+ <tr>
+ <td>Apps are not shut down during backup.</td>
+ <td>The system shuts down the app during backup.</td>
+ </tr>
+ <tr>
+ <td>Backup data is stored in <a href="{@docRoot}google/backup/index.html">Android Backup Service</a> limited to 5MB per app.</td>
+ <td>Backup data is stored in the user's Google Drive limited to 25MB per app.</td>
+ </tr>
+ <tr>
+ <td>Related API methods are not filed based:<ul>
+ <li>{@link android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
+ <li>{@link android.app.backup.BackupAgent#onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()}</ul></td>
+ <td>Related API methods are filed based:<ul>
+<li>{@link android.app.backup.BackupAgent#onFullBackup(FullBackupDataOutput) onFullBackup()}
+<li>{@link android.app.backup.BackupAgent#onRestoreFile(ParcelFileDescriptor,long,File,int,long,long) onRestoreFile()}</ul></td>
+ </tr>
+</table>
+
+<h2 id="ImplementingBackup">Implementing Key/Value Backup</h2>
+<p>To backup your application data, you need to implement a backup agent. Your backup
+agent is called by the Backup Manager both during backup and restore.</p>
+
+<p>To implement a backup agent, you must:</p>
+
+<ol>
+ <li>Declare your backup agent in your manifest file with the <a
+href="{@docRoot}guide/topics/manifest/application-element.html#agent">{@code
+android:backupAgent}</a> attribute.</li>
+ <li>Register your application with <a href="{@docRoot}google/backup/index.html">Android
+ Backup Service</a></li>
+ <li>Define a backup agent by either:</p>
+ <ol type="a">
+ <li><a href="#BackupAgent">Extending BackupAgent</a>
+ <p>The {@link android.app.backup.BackupAgent} class provides the central interface with
+which your application communicates with the Backup Manager. If you extend this class
+directly, you must override {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} and {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()} to handle the backup and restore operations for your data.</p>
+ <p><em>Or</em></p>
+ <li><a href="#BackupAgentHelper">Extending BackupAgentHelper</a>
+ <p>The {@link android.app.backup.BackupAgentHelper} class provides a convenient
+wrapper around the {@link android.app.backup.BackupAgent} class, which minimizes the amount of code
+you need to write. In your {@link android.app.backup.BackupAgentHelper}, you must use one or more
+"helper" objects, which automatically backup and restore certain types of data, so that you do not
+need to implement {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} and {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}.</p>
+ <p>Android currently provides backup helpers that will backup and restore complete files
+from {@link android.content.SharedPreferences} and <a
+href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>.</p>
+ </li>
+ </ol>
+ </li>
+</ol>
+
+<h2 id="BackupManifest">Declaring the backup agent in your manifest</h2>
+
+<p>This is the easiest step, so once you've decided on the class name for your backup agent, declare
+it in your manifest with the <a
+href="{@docRoot}guide/topics/manifest/application-element.html#agent">{@code
+android:backupAgent}</a> attribute in the <a
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code
+<application>}</a> tag.</p>
+
+<p>For example:</p>
+
+<pre>
+<manifest ... >
+ ...
+ <application android:label="MyApplication"
+ <b>android:backupAgent="MyBackupAgent"</b>>
+ <activity ... >
+ ...
+ </activity>
+ </application>
+</manifest>
+</pre>
+
+<p>Another attribute you might want to use is <a
+href="{@docRoot}guide/topics/manifest/application-element.html#restoreany">{@code
+android:restoreAnyVersion}</a>. This attribute takes a boolean value to indicate whether you
+want to restore the application data regardless of the current application version compared to the
+version that produced the backup data. (The default value is "{@code false}".) See <a
+href="#RestoreVersion">Checking the Restore Data Version</a> for more information.</p>
+
+<p class="note"><strong>Note:</strong> The backup service and the APIs you must use are
+available only on devices running API Level 8 (Android 2.2) or greater, so you should also
+set your <a
+href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code android:minSdkVersion}</a>
+attribute to "8".</p>
+
+
+
+
+<h2 id="BackupKey">Registering for Android Backup Service</h2>
+
+<p>Google provides a backup transport with <a
+href="{@docRoot}google/backup/index.html">Android Backup Service</a> for most
+Android-powered devices running Android 2.2 or greater.</p>
+
+<p>In order for your application to perform backup using Android Backup Service, you must
+register your application with the service to receive a Backup Service Key, then
+declare the Backup Service Key in your Android manifest.</p>
+
+<p>To get your Backup Service Key, <a
+href="{@docRoot}google/backup/signup.html">register for Android Backup Service</a>.
+When you register, you will be provided a Backup Service Key and the appropriate {@code
+<meta-data>} XML code for your Android manifest file, which you must include as a child of the
+{@code <application>} element. For example:</p>
+
+<pre>
+<application android:label="MyApplication"
+ android:backupAgent="MyBackupAgent">
+ ...
+ <meta-data android:name="com.google.android.backup.api_key"
+ android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv_4DIsEIyQ" />
+</application>
+</pre>
+
+<p>The <code>android:name</code> must be <code>"com.google.android.backup.api_key"</code> and
+the <code>android:value</code> must be the Backup Service Key received from the Android Backup
+Service registration.</p>
+
+<p>If you have multiple applications, you must register each one, using the respective package
+name.</p>
+
+<p class="note"><strong>Note:</strong> The backup transport provided by Android Backup Service is
+not guaranteed to be available
+on all Android-powered devices that support backup. Some devices might support backup
+using a different transport, some devices might not support backup at all, and there is no way for
+your application to know what transport is used on the device. However, if you implement backup for
+your application, you should always include a Backup Service Key for Android Backup Service so
+your application can perform backup when the device uses the Android Backup Service transport. If
+the device does not use Android Backup Service, then the {@code <meta-data>} element with the
+Backup Service Key is ignored.</p>
+
+
+
+
+<h2 id="BackupAgent">Extending BackupAgent</h2>
+
+<p>Most applications shouldn't need to extend the {@link android.app.backup.BackupAgent} class
+directly, but should instead <a href="#BackupAgentHelper">extend BackupAgentHelper</a> to take
+advantage of the built-in helper classes that automatically backup and restore your files. However,
+you might want to extend {@link android.app.backup.BackupAgent} directly if you need to:</p>
+<ul>
+ <li>Version your data format. For instance, if you anticipate the need to revise the
+format in which you write your application data, you can build a backup agent to cross-check your
+application version during a restore operation and perform any necessary compatibility work if the
+version on the device is different than that of the backup data. For more information, see <a
+href="#RestoreVersion">Checking the Restore Data Version</a>.</li>
+ <li>Instead of backing up an entire file, you can specify the portions of data the should be
+backed up and how each portion is then restored to the device. (This can also help you manage
+different versions, because you read and write your data as unique entities, rather than
+complete files.)</li>
+ <li>Back up data in a database. If you have an SQLite database that you want to restore when
+the user re-installs your application, you need to build a custom {@link
+android.app.backup.BackupAgent} that reads the appropriate data during a backup operation, then
+create your table and insert the data during a restore operation.</li>
+</ul>
+
+<p>If you don't need to perform any of the tasks above and want to back up complete files from
+{@link android.content.SharedPreferences} or <a
+href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>, you
+should skip to <a href="#BackupAgentHelper">Extending BackupAgentHelper</a>.</p>
+
+
+
+<h3 id="RequiredMethods">Required methods</h3>
+
+<p>When you create a backup agent by extending {@link android.app.backup.BackupAgent}, you
+must implement the following callback methods:</p>
+
+<dl>
+ <dt>{@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()}</dt>
+ <dd>The Backup Manager calls this method after you <a href="#RequestingBackup">request a
+backup</a>. In this method, you read your application data from the device and pass the data you
+want to back up to the Backup Manager, as described below in <a href="#PerformingBackup">Performing
+backup</a>.</dd>
+
+ <dt>{@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}</dt>
+ <dd>The Backup Manager calls this method during a restore operation (you can <a
+href="#RequestingRestore">request a restore</a>, but the system automatically performs restore when
+the user re-installs your application). When it calls this method, the Backup Manager delivers your
+backup data, which you then restore to the device, as described below in <a
+href="#PerformingRestore">Performing restore</a>.</dd>
+</dl>
+
+
+
+<h3 id="PerformingBackup">Performing backup</h3>
+
+
+<p>When it's time to back up your application data, the Backup Manager calls your {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method. This is where you must provide your application data to the Backup Manager so
+it can be saved to cloud storage.</p>
+
+<p>Only the Backup Manager can call your backup agent's {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method. Each time that your application data changes and you want to perform a backup,
+you must request a backup operation by calling {@link
+android.app.backup.BackupManager#dataChanged()} (see <a href="#RequestingBackup">Requesting
+Backup</a> for more information). A backup request does not result in an immediate call to your
+{@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method. Instead, the Backup Manager waits for an appropriate time, then performs
+backup for all applications that have requested a backup since the last backup was performed.</p>
+
+<p class="note"><strong>Tip:</strong> While developing your application, you can initiate an
+immediate backup operation from the Backup Manager with the <a
+href="{@docRoot}tools/help/bmgr.html">{@code bmgr} tool</a>.</p>
+
+<p>When the Backup Manager calls your {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method, it passes three parameters:</p>
+
+<dl>
+ <dt>{@code oldState}</dt>
+ <dd>An open, read-only {@link android.os.ParcelFileDescriptor} pointing to the last backup
+state provided by your application. This is not the backup data from cloud storage, but a
+local representation of the data that was backed up the last time {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} was called (as defined by {@code newState}, below, or from {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}—more about this in the next section). Because {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} does not allow you to read existing backup data in
+the cloud storage, you can use this local representation to determine whether your data has changed
+since the last backup.</dd>
+ <dt>{@code data}</dt>
+ <dd>A {@link android.app.backup.BackupDataOutput} object, which you use to deliver your backup
+data to the Backup Manager.</dd>
+ <dt>{@code newState}</dt>
+ <dd>An open, read/write {@link android.os.ParcelFileDescriptor} pointing to a file in which
+you must write a representation of the data that you delivered to {@code data} (a representation
+can be as simple as the last-modified timestamp for your file). This object is
+returned as {@code oldState} the next time the Backup Manager calls your {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method. If you do not write your backup data to {@code newState}, then {@code oldState}
+will point to an empty file next time Backup Manager calls {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()}.</dd>
+</dl>
+
+<p>Using these parameters, you should implement your {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method to do the following:</p>
+
+<ol>
+ <li>Check whether your data has changed since the last backup by comparing {@code oldState} to
+your current data. How you read data in {@code oldState} depends on how you originally wrote it to
+{@code newState} (see step 3). The easiest way to record the state of a file is with its
+last-modified timestamp. For example, here's how you can read and compare a timestamp from {@code
+oldState}:
+ <pre>
+// Get the oldState input stream
+FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
+DataInputStream in = new DataInputStream(instream);
+
+try {
+ // Get the last modified timestamp from the state file and data file
+ long stateModified = in.readLong();
+ long fileModified = mDataFile.lastModified();
+
+ if (stateModified != fileModified) {
+ // The file has been modified, so do a backup
+ // Or the time on the device changed, so be safe and do a backup
+ } else {
+ // Don't back up because the file hasn't changed
+ return;
+ }
+} catch (IOException e) {
+ // Unable to read state file... be safe and do a backup
+}
+</pre>
+ <p>If nothing has changed and you don't need to back up, skip to step 3.</p>
+ </li>
+ <li>If your data has changed, compared to {@code oldState}, write the current data to
+{@code data} to back it up to the cloud storage.
+ <p>You must write each chunk of data as an "entity" in the {@link
+android.app.backup.BackupDataOutput}. An entity is a flattened binary data
+record that is identified by a unique key string. Thus, the data set that you back up is
+conceptually a set of key-value pairs.</p>
+ <p>To add an entity to your backup data set, you must:</p>
+ <ol>
+ <li>Call {@link android.app.backup.BackupDataOutput#writeEntityHeader(String,int)
+writeEntityHeader()}, passing a unique string key for the data you're about to write and the data
+size.</li>
+ <li>Call {@link android.app.backup.BackupDataOutput#writeEntityData(byte[],int)
+writeEntityData()}, passing a byte buffer that contains your data and the number of bytes to write
+from the buffer (which should match the size passed to {@link
+android.app.backup.BackupDataOutput#writeEntityHeader(String,int) writeEntityHeader()}).</li>
+ </ol>
+ <p>For example, the following code flattens some data into a byte stream and writes it into a
+single entity:</p>
+ <pre>
+// Create buffer stream and data output stream for our data
+ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
+DataOutputStream outWriter = new DataOutputStream(bufStream);
+// Write structured data
+outWriter.writeUTF(mPlayerName);
+outWriter.writeInt(mPlayerScore);
+// Send the data to the Backup Manager via the BackupDataOutput
+byte[] buffer = bufStream.toByteArray();
+int len = buffer.length;
+data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
+data.writeEntityData(buffer, len);
+</pre>
+ <p>Perform this for each piece of data that you want to back up. How you divide your data into
+entities is up to you (and you might use just one entity).</p>
+ </li>
+ <li>Whether or not you perform a backup (in step 2), write a representation of the current data to
+the {@code newState} {@link android.os.ParcelFileDescriptor}. The Backup Manager retains this object
+locally as a representation of the data that is currently backed up. It passes this back to you as
+{@code oldState} the next time it calls {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} so you can determine whether another backup is necessary (as handled in step 1). If you
+do not write the current data state to this file, then
+{@code oldState} will be empty during the next callback.
+ <p>The following example saves a representation of the current data into {@code newState} using
+the file's last-modified timestamp:</p>
+ <pre>
+FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
+DataOutputStream out = new DataOutputStream(outstream);
+
+long modified = mDataFile.lastModified();
+out.writeLong(modified);
+</pre>
+ </li>
+</ol>
+
+<p class="caution"><strong>Caution:</strong> If your application data is saved to a file, make sure
+that you use synchronized statements while accessing the file so that your backup agent does not
+read the file while an Activity in your application is also writing the file.</p>
+
+
+
+
+<h3 id="PerformingRestore">Performing restore</h3>
+
+<p>When it's time to restore your application data, the Backup Manager calls your backup
+agent's {@link android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()} method. When it calls this method, the Backup Manager delivers your backup data so
+you can restore it onto the device.</p>
+
+<p>Only the Backup Manager can call {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}, which happens automatically when the system installs your application and
+finds existing backup data. However, you can request a restore operation for
+your application by calling {@link
+android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()} (see <a
+href="#RequestingRestore">Requesting restore</a> for more information).</p>
+
+<p class="note"><strong>Note:</strong> While developing your application, you can also request a
+restore operation with the <a href="{@docRoot}tools/help/bmgr.html">{@code bmgr}
+tool</a>.</p>
+
+<p>When the Backup Manager calls your {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()} method, it passes three parameters:</p>
+
+<dl>
+ <dt>{@code data}</dt>
+ <dd>A {@link android.app.backup.BackupDataInput}, which allows you to read your backup
+data.</dd>
+ <dt>{@code appVersionCode}</dt>
+ <dd>An integer representing the value of your application's <a
+href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
+manifest attribute, as it was when this data was backed up. You can use this to cross-check the
+current application version and determine if the data format is compatible. For more
+information about using this to handle different versions of restore data, see the section
+below about <a href="#RestoreVersion">Checking the Restore Data Version</a>.</dd>
+ <dt>{@code newState}</dt>
+ <dd>An open, read/write {@link android.os.ParcelFileDescriptor} pointing to a file in which
+you must write the final backup state that was provided with {@code data}. This object is
+returned as {@code oldState} the next time {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} is called. Recall that you must also write the same {@code newState} object in the
+{@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} callback—also doing it here ensures that the {@code oldState} object given to
+{@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} is valid even the first time {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} is called after the device is restored.</dd>
+</dl>
+
+<p>In your implementation of {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}, you should call {@link android.app.backup.BackupDataInput#readNextHeader()} on the
+{@code data} to iterate
+through all entities in the data set. For each entity found, do the following:</p>
+
+<ol>
+ <li>Get the entity key with {@link android.app.backup.BackupDataInput#getKey()}.</li>
+ <li>Compare the entity key to a list of known key values that you should have declared as static
+final strings inside your {@link android.app.backup.BackupAgent} class. When the key matches one of
+your known key strings, enter into a statement to extract the entity data and save it to the device:
+ <ol>
+ <li>Get the entity data size with {@link
+android.app.backup.BackupDataInput#getDataSize()} and create a byte array of that size.</li>
+ <li>Call {@link android.app.backup.BackupDataInput#readEntityData(byte[],int,int)
+readEntityData()} and pass it the byte array, which is where the data will go, and specify the
+start offset and the size to read.</li>
+ <li>Your byte array is now full and you can read the data and write it to the device
+however you like.</li>
+ </ol>
+ </li>
+ <li>After you read and write your data back to the device, write the state of your data to the
+{@code newState} parameter the same as you do during {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()}.
+</ol>
+
+<p>For example, here's how you can restore the data backed up by the example in the previous
+section:</p>
+
+<pre>
+@Override
+public void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState) throws IOException {
+ // There should be only one entity, but the safest
+ // way to consume it is using a while loop
+ while (data.readNextHeader()) {
+ String key = data.getKey();
+ int dataSize = data.getDataSize();
+
+ // If the key is ours (for saving top score). Note this key was used when
+ // we wrote the backup entity header
+ if (TOPSCORE_BACKUP_KEY.equals(key)) {
+ // Create an input stream for the BackupDataInput
+ byte[] dataBuf = new byte[dataSize];
+ data.readEntityData(dataBuf, 0, dataSize);
+ ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
+ DataInputStream in = new DataInputStream(baStream);
+
+ // Read the player name and score from the backup data
+ mPlayerName = in.readUTF();
+ mPlayerScore = in.readInt();
+
+ // Record the score on the device (to a file or something)
+ recordScore(mPlayerName, mPlayerScore);
+ } else {
+ // We don't know this entity key. Skip it. (Shouldn't happen.)
+ data.skipEntityData();
+ }
+ }
+
+ // Finally, write to the state blob (newState) that describes the restored data
+ FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
+ DataOutputStream out = new DataOutputStream(outstream);
+ out.writeUTF(mPlayerName);
+ out.writeInt(mPlayerScore);
+}
+</pre>
+
+<p>In this example, the {@code appVersionCode} parameter passed to {@link
+android.app.backup.BackupAgent#onRestore onRestore()} is not used. However, you might want to use
+it if you've chosen to perform backup when the user's version of the application has actually moved
+backward (for example, the user went from version 1.5 of your app to 1.0). For more information, see
+the section about <a href="#RestoreVersion">Checking the Restore Data Version</a>.</p>
+
+<div class="special">
+<p>For an example implementation of {@link android.app.backup.BackupAgent}, see the <a
+href="{@docRoot}resources/samples/BackupRestore/src/com/example/android/backuprestore/ExampleAgent.html">{@code
+ExampleAgent}</a> class in the <a
+href="{@docRoot}resources/samples/BackupRestore/index.html">Backup and Restore</a> sample
+application.</p>
+</div>
+
+
+
+
+
+
+<h2 id="BackupAgentHelper">Extending BackupAgentHelper</h2>
+
+<p>You should build your backup agent using {@link android.app.backup.BackupAgentHelper} if you want
+to back up complete files (from either {@link android.content.SharedPreferences} or <a
+href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>).
+Building your backup agent with {@link android.app.backup.BackupAgentHelper} requires far less
+code than extending {@link android.app.backup.BackupAgent}, because you don't have to implement
+{@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} and {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}.</p>
+
+<p>Your implementation of {@link android.app.backup.BackupAgentHelper} must
+use one or more backup helpers. A backup helper is a specialized
+component that {@link android.app.backup.BackupAgentHelper} summons to perform backup and
+restore operations for a particular type of data. The Android framework currently provides two
+different helpers:</p>
+<ul>
+ <li>{@link android.app.backup.SharedPreferencesBackupHelper} to backup {@link
+android.content.SharedPreferences} files.</li>
+ <li>{@link android.app.backup.FileBackupHelper} to backup files from <a
+href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>.</li>
+</ul>
+
+<p>You can include multiple helpers in your {@link android.app.backup.BackupAgentHelper}, but only
+one helper is needed for each data type. That is, if you have multiple {@link
+android.content.SharedPreferences} files, then you need only one {@link
+android.app.backup.SharedPreferencesBackupHelper}.</p>
+
+<p>For each helper you want to add to your {@link android.app.backup.BackupAgentHelper}, you must do
+the following during your {@link android.app.backup.BackupAgent#onCreate()} method:</p>
+<ol>
+ <li>Instantiate in instance of the desired helper class. In the class constructor, you must
+specify the appropriate file(s) you want to backup.</li>
+ <li>Call {@link android.app.backup.BackupAgentHelper#addHelper(String,BackupHelper) addHelper()}
+to add the helper to your {@link android.app.backup.BackupAgentHelper}.</li>
+</ol>
+
+<p>The following sections describe how to create a backup agent using each of the available
+helpers.</p>
+
+
+
+<h3 id="SharedPreferences">Backing up SharedPreferences</h3>
+
+<p>When you instantiate a {@link android.app.backup.SharedPreferencesBackupHelper}, you must
+include the name of one or more {@link android.content.SharedPreferences} files.</p>
+
+<p>For example, to back up a {@link android.content.SharedPreferences} file named
+"user_preferences", a complete backup agent using {@link android.app.backup.BackupAgentHelper} looks
+like this:</p>
+
+<pre>
+public class MyPrefsBackupAgent extends BackupAgentHelper {
+ // The name of the SharedPreferences file
+ static final String PREFS = "user_preferences";
+
+ // A key to uniquely identify the set of backup data
+ static final String PREFS_BACKUP_KEY = "prefs";
+
+ // Allocate a helper and add it to the backup agent
+ @Override
+ public void onCreate() {
+ SharedPreferencesBackupHelper helper =
+ new SharedPreferencesBackupHelper(this, PREFS);
+ addHelper(PREFS_BACKUP_KEY, helper);
+ }
+}
+</pre>
+
+<p>That's it! That's your entire backup agent. The {@link
+android.app.backup.SharedPreferencesBackupHelper} includes all the code
+needed to backup and restore a {@link android.content.SharedPreferences} file.</p>
+
+<p>When the Backup Manager calls {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} and {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()}, {@link android.app.backup.BackupAgentHelper} calls your backup helpers to perform
+backup and restore for your specified files.</p>
+
+<p class="note"><strong>Note:</strong> The methods of {@link android.content.SharedPreferences}
+are threadsafe, so
+you can safely read and write the shared preferences file from your backup agent and
+other activities.</p>
+
+
+
+<h3 id="Files">Backing up other files</h3>
+
+<p>When you instantiate a {@link android.app.backup.FileBackupHelper}, you must include the name of
+one or more files that are saved to your application's <a
+href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>
+(as specified by {@link android.content.ContextWrapper#getFilesDir()}, which is the same
+location where {@link android.content.Context#openFileOutput(String,int) openFileOutput()} writes
+files).</p>
+
+<p>For example, to backup two files named "scores" and "stats," a backup agent using {@link
+android.app.backup.BackupAgentHelper} looks like this:</p>
+
+<pre>
+public class MyFileBackupAgent extends BackupAgentHelper {
+ // The name of the file
+ static final String TOP_SCORES = "scores";
+ static final String PLAYER_STATS = "stats";
+
+ // A key to uniquely identify the set of backup data
+ static final String FILES_BACKUP_KEY = "myfiles";
+
+ // Allocate a helper and add it to the backup agent
+ @Override
+ public void onCreate() {
+ FileBackupHelper helper = new FileBackupHelper(this,
+ TOP_SCORES, PLAYER_STATS);
+ addHelper(FILES_BACKUP_KEY, helper);
+ }
+}
+</pre>
+
+<p>The {@link android.app.backup.FileBackupHelper} includes all the code necessary to backup and
+restore files that are saved to your application's <a
+href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal storage</a>..</p>
+
+<p>However, reading and writing to files on internal storage is <strong>not threadsafe</strong>. To
+ensure that your backup agent does not read or write your files at the same time as your activities,
+you must use synchronized statements each time you perform a read or write. For example,
+in any Activity where you read and write the file, you need an object to use as the intrinsic
+lock for the synchronized statements:</p>
+
+<pre>
+// Object for intrinsic lock
+static final Object sDataLock = new Object();
+</pre>
+
+<p>Then create a synchronized statement with this lock each time you read or write the files. For
+example, here's a synchronized statement for writing the latest score in a game to a file:</p>
+
+<pre>
+try {
+ synchronized (MyActivity.sDataLock) {
+ File dataFile = new File({@link android.content.Context#getFilesDir()}, TOP_SCORES);
+ RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
+ raFile.writeInt(score);
+ }
+} catch (IOException e) {
+ Log.e(TAG, "Unable to write to file");
+}
+</pre>
+
+<p>You should synchronize your read statements with the same lock.</p>
+
+<p>Then, in your {@link android.app.backup.BackupAgentHelper}, you must override {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} and {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()} to synchronize the backup and restore operations with the same
+intrinsic lock. For example, the {@code MyFileBackupAgent} example from above needs the following
+methods:</p>
+
+<pre>
+@Override
+public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ // Hold the lock while the FileBackupHelper performs backup
+ synchronized (MyActivity.sDataLock) {
+ super.onBackup(oldState, data, newState);
+ }
+}
+
+@Override
+public void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState) throws IOException {
+ // Hold the lock while the FileBackupHelper restores the file
+ synchronized (MyActivity.sDataLock) {
+ super.onRestore(data, appVersionCode, newState);
+ }
+}
+</pre>
+
+<p>That's it. All you need to do is add your {@link android.app.backup.FileBackupHelper} in the
+{@link android.app.backup.BackupAgent#onCreate()} method and override {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} and {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+onRestore()} to synchronize read and write operations.</p>
+
+<div class="special">
+<p>For an example implementation of {@link
+android.app.backup.BackupAgentHelper} with {@link android.app.backup.FileBackupHelper}, see the
+{@code FileHelperExampleAgent} class in the <a
+href="{@docRoot}resources/samples/BackupRestore/index.html">Backup and Restore</a> sample
+application.</p>
+</div>
+
+
+
+
+
+
+<h2 id="RestoreVersion">Checking the restore data version</h2>
+
+<p>When the Backup Manager saves your data to cloud storage, it automatically includes the version
+of your application, as defined by your manifest file's <a
+href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
+attribute. Before the Backup Manager calls your backup agent to restore your data, it
+looks at the <a
+href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
+android:versionCode}</a> of the installed application and compares it to the value
+recorded in the restore data set. If the version recorded in the restore data set is
+<em>newer</em> than the application version on the device, then the user has downgraded their
+application. In this case, the Backup Manager will abort the restore operation for your application
+and not call your {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
+method, because the restore set is considered meaningless to an older version.</p>
+
+<p>You can override this behavior with the <a
+href="{@docRoot}guide/topics/manifest/application-element.html#restoreany">{@code
+android:restoreAnyVersion}</a> attribute. This attribute is either "{@code true}" or "{@code
+false}" to indicate whether you want to restore the application regardless of the restore set
+version. The default value is "{@code false}". If you define this to be "{@code true}" then the
+Backup Manager will ignore the <a
+href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
+and call your {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
+method in all cases. In doing so, you can manually check for the version difference in your {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
+method and take any steps necessary to make the data compatible if the versions conflict.</p>
+
+<p>To help you handle different versions during a restore operation, the {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
+method passes you the version code included with the restore data set as the {@code appVersionCode}
+parameter. You can then query the current application's version code with the {@link
+android.content.pm.PackageInfo#versionCode PackageInfo.versionCode} field. For example:</p>
+
+<pre>
+PackageInfo info;
+try {
+ String name = {@link android.content.ContextWrapper#getPackageName() getPackageName}();
+ info = {@link android.content.ContextWrapper#getPackageManager
+getPackageManager}().{@link android.content.pm.PackageManager#getPackageInfo(String,int)
+getPackageInfo}(name,0);
+} catch (NameNotFoundException nnfe) {
+ info = null;
+}
+
+int version;
+if (info != null) {
+ version = info.versionCode;
+}
+</pre>
+
+<p>Then simply compare the {@code version} acquired from {@link android.content.pm.PackageInfo}
+to the {@code appVersionCode} passed into {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}.
+</p>
+
+<p class="caution"><strong>Caution:</strong> Be certain you understand the consequences of setting
+<a href="{@docRoot}guide/topics/manifest/application-element.html#restoreany">{@code
+android:restoreAnyVersion}</a> to "{@code true}" for your application. If each version of your
+application that supports backup does not properly account for variations in your data format during
+{@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()},
+then the data on the device could be saved in a format incompatible with the version currently
+installed on the device.</p>
+
+
+
+<h2 id="RequestingBackup">Requesting backup</h2>
+
+<p>You can request a backup operation at any time by calling {@link
+android.app.backup.BackupManager#dataChanged()}. This method notifies the Backup Manager that you'd
+like to backup your data using your backup agent. The Backup Manager then calls your backup
+agent's {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()} method at an opportune time in the future. Typically, you should
+request a backup each time your data changes (such as when the user changes an application
+preference that you'd like to back up). If you call {@link
+android.app.backup.BackupManager#dataChanged()} several times consecutively, before the Backup
+Manager requests a backup from your agent, your agent still receives just one call to {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+onBackup()}.</p>
+
+<p class="note"><strong>Note:</strong> While developing your application, you can request a
+backup and initiate an immediate backup operation with the <a
+href="{@docRoot}tools/help/bmgr.html">{@code bmgr}
+tool</a>.</p>
+
+
+<h2 id="RequestingRestore">Requesting restore</h2>
+
+<p>During the normal life of your application, you shouldn't need to request a restore operation.
+They system automatically checks for backup data and performs a restore when your application is
+installed. However, you can manually request a restore operation by calling {@link
+android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()}, if necessary. In
+which case, the Backup Manager calls your {@link
+android.app.backup.BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor) onRestore()}
+implementation, passing the data from the current set of backup data.</p>
+
+<p class="note"><strong>Note:</strong> While developing your application, you can request a
+restore operation with the <a href="{@docRoot}tools/help/bmgr.html">{@code bmgr}
+tool</a>.</p>
+
+
+<h2 id="Migrating">Migrating to Auto Backup</h2>
+<p>You can transition your app to full-data backups by setting <a href="{@docRoot}reference/android/R.attr.html#fullBackupOnly">android:fullBackupOnly</a> to <code>true</code> in the <code><application></code> element in the manifest file. When
+running on a device with Android 5.1 (API level 22) or lower, your app ignores
+this value in the manifest, and continues performing Key/Value Backups. When
+running on a device with Android 6.0 (API level 23) or higher, your app performs
+Auto Backup instead of Key/Value Backup.
\ No newline at end of file
diff --git a/docs/html/guide/topics/data/testingbackup.jd b/docs/html/guide/topics/data/testingbackup.jd
new file mode 100644
index 0000000..6ff5837
--- /dev/null
+++ b/docs/html/guide/topics/data/testingbackup.jd
@@ -0,0 +1,181 @@
+page.title=Testing Backup and Restore
+page.tags=backup, marshmallow, androidm
+page.keywords=backup, restore, testing
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#HowBackupWorks">How backup works</a></li>
+ <li><a href="#Prerequisites">Prerequisites</a></li>
+ <li><a href="#Preparing">Preparing your device or emulator</a></li>
+ <li><a href="#TestingBackup">Testing backup</a></li>
+ <li><a href="#TestingRestore">Testing restore</a></li>
+ <li><a href="#Troubleshooting">Troubleshooting</a></li>
+ </ol>
+
+</div>
+</div>
+
+<p>This page shows you how to manually trigger Auto Backup, Key/Value Backup, and restore operations to
+ensure your app saves and restores data properly.
+
+<h2 id="HowBackupWorkso">How backup works</h2>
+<p>The section describes various pieces in the Android backup framework and how they
+interact with apps that support Auto Backup and Key/Value Backup. During the app
+development phase, most of the inner working of the framework were abstracted away, so you didn't need to know this information. However, during the
+testing phase, an understanding of these concepts is important.
+
+<p>The following diagram illustrates how data flows during backup and restore:
+
+<img src="images/backup-framework.png" alt="backup-framework">
+
+<p>The <em>Backup Manager Service</em> is an Android system
+service which orchestrates and initiates backup and restore operations. The service
+is accessible through the {@link android.app.backup.BackupManager}
+API. During a backup operation, the service queries your app for backup data,
+then hands it to the <em>backup transport</em>, which then archives the data.
+During a restore operation, the backup manager service retrieves the backup data
+from the backup transport and restores the data to the device.
+
+<p><em>Backup Transports</em> are Android components that are responsible
+for storing and retrieving backups. An Android device can have zero or more
+backup transports, but only one of those transports can be marked active. The
+available backup transports may differ from device to device (due to
+customizations by device manufacturers and service providers), but most Google
+Play enabled devices ship with the following transports:
+</p><ul>
+<li><strong>Google GMS Transport</strong>(default) - the active backup
+transport on most devices, part of <a href="https://www.android.com/gms/">Google Mobile Services</a>. This documentation assumes that users are using the
+Google GMS transport. This transport stores Auto Backup data in a private folder in the
+user's Google Drive account. Key/Value Backup data is stored in the <a href="{@docRoot}google/backup/index.html">Android Backup Service</a>.
+<li><strong>Local Transport</strong> - stores backup data locally on the device.
+This transport is typically used for development/debugging purposes and is not
+useful in the real world.</li></ul>
+
+<p>If a device does not have any backup transports, then the data cannot be
+backed up. Your app is not adversely affected.
+
+<p class="caution"><strong>Caution:</strong> Because the backup transport
+can differ from device to device, Android cannot guarantee the security
+of your data while using backup. Be cautious about using backup to store
+sensitive data, such as usernames and passwords.
+
+<h2 id="Prerequisites">Prerequisites</h2>
+<p>You need to know a bit about the following tools:
+
+<ul>
+ <li><a href="{@docRoot}studio/command-line/adb.html">adb</a>
+- to run commands on the device or emulator
+ <li><a href="{@docRoot}studio/command-line/bmgr.html">bmgr</a> - to
+perform various backup and restore operations
+ <li><a href="{@docRoot}studio/command-line/logcat.html">logcat</a>
+- to see the output of backup and restore operations.</li></ul>
+
+<h2 id="Preparing">Preparing your device or
+emulator</h2>
+<p>Prepare your device or emulator for backup testing by working through the
+following checklist:
+<ul>
+ <li>For Auto Backup, check that you are using a device or emulator running
+Android 6.0 (API level 23) or higher.</li>
+ <li>For Key/Value Backup, check that you are using a device or emulator running
+Android 2.2 (API level 8) or higher.</li>
+ <li>Check that backup and restore is enabled on the device or emulator. There
+are two ways to check: <ul>
+ <li>On the device, go to <strong>Settings -> Backup & Restore</strong>.
+ <li>From adb shell, run <code>bmgr enable</code></li></ul>
+ <p>On physical devices, backup and restore is typically enabled during the
+initial setup wizard. Emulators do not run the setup wizard, so don't forget to
+enable backup and specify a backup account in device settings.
+ <li>Make sure the GMS Backup Transport is available and active by running the
+command from adb shell:
+ <pre class="prettyprint">$ bmgr list transports</pre>
+ <p>Then, check the console for the following output:
+ <pre class="prettyprint">android/com.android.internal.backup.LocalTransport
+* com.google.android.gms/.backup.BackupTransportService</pre>
+ <p>Physical devices without Google Play and emulators without Google APIs
+might not include the GMS Backup Transport. This article assumes you are using
+the GMS Backup Transport. You can test backup and restore with other backup
+transports, but the procedure and output can differ.
+</ul>
+
+<h2 id="TestingBackup">Testing backup</h2>
+<p>To initiate a backup of your app, run the following command:
+
+<pre class="prettyprint">$ bmgr backupnow <PACKAGE></pre>
+
+<p>The <code>backupnow</code> command runs either a Key/Value Backup or Auto Backup depending on
+the package's manifest declarations. Check logcat to see the output of the
+backup procedure. For example:
+
+<pre class="prettyprint">D/BackupManagerService: fullTransportBackup()
+I/GmsBackupTransport: Attempt to do full backup on <PACKAGE>
+
+---- or ----
+
+V/BackupManagerService: Scheduling immediate backup pass
+D/PerformBackupTask: starting key/value Backup of BackupRequest{pkg=<PACKAGE>}
+</pre>
+
+<p>If the <code>backupnow</code> command is not available on your device, you need to run one
+of the following commands:
+<ul>
+ <li>For Auto Backups, run: <code>bmgr fullbackup <PACKAGE></code>
+ <li>For Key/Value Backups, schedule and run your backup with the following
+commands:
+ <pre class="prettyprint">$ bmgr backup <PACKAGE>
+$ bmgr run</pre>
+ <p><code>bmgr backup</code> adds your app to the Backup Manager's queue. <code>bmgr run</code> initiates the
+backup operation, which forces the Backup Manager to perform all backup requests
+that are in its queue.
+ </li></ul>
+
+<h2 id="TestingRestore">Testing restore</h2>
+<p>To manually initiate a restore, run the following command:
+
+<pre class="prettyprint">$ bmgr restore <PACKAGE></pre>
+
+<p>Warning: This action stops your app and wipes its data before performing the
+restore operation.
+
+<p>Then, check logcat to see the output of the restore procedure. For example:
+
+<pre class="prettyprint">V/BackupManagerService: beginRestoreSession: pkg=<PACKAGE> transport=null
+V/RestoreSession: restorePackage pkg=<PACKAGE> token=368abb4465c5c683
+...
+I/BackupManagerService: Restore complete.
+</pre>
+
+<p>You also can test automatic restore for your app by uninstalling and
+reinstalling your app either with <code>adb</code> or through the Google
+Play Store app.
+
+<h2 id="Troubleshooting">Troubleshooting</h2>
+<p><strong>Exceeded Quota</strong>
+
+<p>If you see the the following messages in logcat:
+
+<pre class="prettyprint">I/PFTBT: Transport rejected backup of <PACKAGE>, skipping
+
+--- or ---
+
+I/PFTBT: Transport quota exceeded for package: <PACKAGE>
+</pre>
+
+<p>Your app has exceeded the quota and has been banned from backing up
+data on this device. To lift the ban, either factory reset your device or change
+the backup account.
+
+<p><strong>Full Backup Not Possible</strong>
+
+<p>If you see the the following message in logcat:
+
+<pre class="prettyprint">I/BackupManagerService: Full backup not currently possible -- key/value backup not yet run?
+</pre>
+
+<p>The fullbackup operation failed because no Key/Value Backup operation has yet
+occurred on the device. Trigger a Key/Value Backup with the command <code>bmgr
+run </code>and then try again.
\ No newline at end of file
diff --git a/docs/html/guide/topics/manifest/provider-element.jd b/docs/html/guide/topics/manifest/provider-element.jd
index 1947849..0e729c3 100644
--- a/docs/html/guide/topics/manifest/provider-element.jd
+++ b/docs/html/guide/topics/manifest/provider-element.jd
@@ -215,17 +215,15 @@
</p></dd>
<dt><a name="multi"></a>{@code android:multiprocess}</dt>
-<dd>Whether or not an instance of the content provider can be created in
-every client process — "{@code true}" if instances can run in multiple
-processes, and "{@code false}" if not. The default value is "{@code false}".
+<dd>If the app runs in multiple processes, this attribute determines whether
+multiple instances of the content provder are created. If <code>true</code>,
+each of the app's processes has its own content provider object. If
+<code>false</code>, the app's processes share only one content provider object.
+The default value is <code>false</code>.
-<p>
-Normally, a content provider is instantiated in the process of the
-application that defined it. However, if this flag is set to "{@code true}",
-the system can create an instance in every process where there's a client
-that wants to interact with it, thus avoiding the overhead of interprocess
-communication.
-</p></dd>
+<p>Setting this flag to <code>true</code> may improve performance by reducing
+the overhead of interprocess communication, but it also increases the memory
+footprint of each process.</p></dd>
<dt><a name="nm"></a>{@code android:name}</dt>
<dd>The name of the class that implements the content provider, a subclass of
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/guide/topics/resources/drawable-resource.jd b/docs/html/guide/topics/resources/drawable-resource.jd
index aae0cba..4587ae4 100644
--- a/docs/html/guide/topics/resources/drawable-resource.jd
+++ b/docs/html/guide/topics/resources/drawable-resource.jd
@@ -1270,7 +1270,7 @@
progressively reveal the image:</p>
<pre>
ImageView imageview = (ImageView) findViewById(R.id.image);
-ClipDrawable drawable = (ClipDrawable) imageview.getDrawable();
+ClipDrawable drawable = (ClipDrawable) imageview.getBackground();
drawable.setLevel(drawable.getLevel() + 1000);
</pre>
diff --git a/docs/html/guide/topics/ui/accessibility/services.jd b/docs/html/guide/topics/ui/accessibility/services.jd
index c6db855..dbc69ef 100644
--- a/docs/html/guide/topics/ui/accessibility/services.jd
+++ b/docs/html/guide/topics/ui/accessibility/services.jd
@@ -79,22 +79,15 @@
as shown in the following sample:</p>
<pre>
-<manifest>
- ...
- <uses-permission ... />
- ...
<application>
- ...
<service android:name=".MyAccessibilityService"
- android:label="@string/accessibility_service_label"
- android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:label="@string/accessibility_service_label">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
- <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
</application>
-</manifest>
</pre>
<p>These declarations are required for all accessibility services deployed on Android 1.6 (API Level
diff --git a/docs/html/guide/topics/ui/drag-drop.jd b/docs/html/guide/topics/ui/drag-drop.jd
index 4a87cd4..9280818 100644
--- a/docs/html/guide/topics/ui/drag-drop.jd
+++ b/docs/html/guide/topics/ui/drag-drop.jd
@@ -157,19 +157,22 @@
</p>
<p>
Your application tells the system to start a drag by calling the
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}
method. This tells the system to start sending drag events. The method also sends the data that
you are dragging.
</p>
<p>
You can call
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}
for any attached View in the current layout. The system only uses the View object to get access
to global settings in your layout.
</p>
<p>
Once your application calls
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()},
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()},
the rest of the process uses events that the system sends to the View objects in your current
layout.
</p>
@@ -183,11 +186,13 @@
</dt>
<dd>
In response to the user's gesture to begin a drag, your application calls
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}
- to tell the system to start a drag. The arguments
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}
- provide the data to be dragged, metadata for this data, and a callback for drawing the
- drag shadow.
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}
+ to tell the system to start a drag. The
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}
+ arguments provide the data to be dragged, metadata for this data, and a callback for drawing
+ the drag shadow.
<p>
The system first responds by calling back to your application to get a drag shadow. It
then displays the drag shadow on the device.
@@ -199,12 +204,13 @@
including a possible drop event, a drag event listener must return <code>true</code>.
This registers the listener with the system. Only registered listeners continue to
receive drag events. At this point, listeners can also change the appearance of their
- View object to show that the listener can accept a drop event.
+ View object to show that the listener can accept the dragged data.
</p>
<p>
If the drag event listener returns <code>false</code>, then it will not receive drag
- events for the current operation until the system sends a drag event with action type
- {@link android.view.DragEvent#ACTION_DRAG_ENDED}. By sending <code>false</code>, the
+ events for the current operation, including the drag event with action type
+ {@link android.view.DragEvent#ACTION_DRAG_ENDED} that will conclude the
+ operation. By sending <code>false</code>, the
listener tells the system that it is not interested in the drag operation and
does not want to accept the dragged data.
</p>
@@ -230,7 +236,8 @@
object's listener a drag event with action type
{@link android.view.DragEvent#ACTION_DROP}. The drag event contains the data that was
passed to the system in the call to
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}
that started the operation. The listener is expected to return boolean <code>true</code> to
the system if code for accepting the drop succeeds.
<p>
@@ -297,7 +304,8 @@
<p>
The {@link android.view.DragEvent} object also contains the data that your application provided
to the system in the call to
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}.
Some of the data is valid only for certain action types. The data that is valid for each action
type is summarized in <a href="#table2">table 2</a>. It is also described in detail with
the event for which it is valid in the section
@@ -316,12 +324,13 @@
<td>
A View object's drag event listener receives this event action type just after the
application calls
-{@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} and
- gets a drag shadow.
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}
+ and gets a drag shadow.
<p>
If the listener wants to continue receiving drag events for this operation, it must
return boolean <code>true</code> to the system.
- <\p>
+ </p>
</td>
</tr>
<tr>
@@ -345,8 +354,7 @@
<td>{@link android.view.DragEvent#ACTION_DRAG_EXITED}</td>
<td>
A View object's drag event listener receives this event action type after it receives a
- {@link android.view.DragEvent#ACTION_DRAG_ENTERED} and at least one
- {@link android.view.DragEvent#ACTION_DRAG_LOCATION} event, and after the user has moved
+ {@link android.view.DragEvent#ACTION_DRAG_ENTERED} event, and after the user has moved
the drag shadow outside the bounding box of the View or into a descendant view that can
accept the data.
</td>
@@ -355,7 +363,8 @@
<td>{@link android.view.DragEvent#ACTION_DROP}</td>
<td>
A View object's drag event listener receives this event action type when the user
- releases the drag shadow over the View object. This action type is only sent to a View
+ releases the drag shadow over the View object and not over its descendant view that can
+ accept the drag data. This action type is only sent to a View
object's listener if the listener returned boolean <code>true</code> in response to the
{@link android.view.DragEvent#ACTION_DRAG_STARTED} drag event. This action type is not
sent if the user releases the drag shadow on a View whose listener is not registered,
@@ -408,8 +417,8 @@
<td>{@link android.view.DragEvent#ACTION_DRAG_ENTERED}</td>
<td style="text-align: center;">X</td>
<td style="text-align: center;">X</td>
- <td style="text-align: center;">X</td>
- <td style="text-align: center;">X</td>
+ <td style="text-align: center;"> </td>
+ <td style="text-align: center;"> </td>
<td style="text-align: center;"> </td>
<td style="text-align: center;"> </td>
</tr>
@@ -442,7 +451,7 @@
</tr>
<tr>
<td>{@link android.view.DragEvent#ACTION_DRAG_ENDED}</td>
- <td style="text-align: center;">X</td>
+ <td style="text-align: center;"> </td>
<td style="text-align: center;">X</td>
<td style="text-align: center;"> </td>
<td style="text-align: center;"> </td>
@@ -472,9 +481,11 @@
The image is called a drag shadow. You create it with methods you declare for a
{@link android.view.View.DragShadowBuilder} object, and then pass it to the system when you
start a drag using
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}.
As part of its response to
- {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()},
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()},
the system invokes the callback methods you've defined in
{@link android.view.View.DragShadowBuilder} to obtain a drag shadow.
</p>
@@ -516,7 +527,8 @@
</dt>
<dd>
The system calls this method immediately after you call
-{@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. Use it
+ {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ startDragAndDrop()}. Use it
to send to the system the dimensions and touch point of the drag shadow. The method has two
arguments:
<dl>
@@ -616,10 +628,10 @@
// Starts the drag
- v.startDrag(dragData, // the data to be dragged
- myShadow, // the drag shadow builder
- null, // no need to use local data
- 0 // flags (not currently used, set to 0)
+ v.startDragAndDrop(dragData, // the data to be dragged
+ myShadow, // the drag shadow builder
+ null, // no need to use local data
+ 0 // flags (not currently used, set to 0)
);
}
@@ -722,8 +734,7 @@
<p>
Note that for an {@link android.view.DragEvent#ACTION_DRAG_STARTED} event, these
the following {@link android.view.DragEvent} methods are not valid:
- {@link android.view.DragEvent#getClipData()}, {@link android.view.DragEvent#getX()},
- {@link android.view.DragEvent#getY()}, and {@link android.view.DragEvent#getResult()}.
+ {@link android.view.DragEvent#getClipData()} and {@link android.view.DragEvent#getResult()}.
</p>
<h3 id="HandleDuring">Handling events during the drag</h3>
<p>
@@ -751,7 +762,9 @@
{@link android.view.DragEvent#ACTION_DRAG_LOCATION}: Once the listener receives an
{@link android.view.DragEvent#ACTION_DRAG_ENTERED} event, and before it receives an
A{@link android.view.DragEvent#ACTION_DRAG_EXITED} event, it receives a new
- {@link android.view.DragEvent#ACTION_DRAG_LOCATION} event every time the touch point moves.
+ {@link android.view.DragEvent#ACTION_DRAG_LOCATION} event immediately after the
+ {@link android.view.DragEvent#ACTION_DRAG_ENTERED} event, and then every time the touch
+ point moves.
The {@link android.view.DragEvent#getX()} and {@link android.view.DragEvent#getY()} methods
return the X and Y coordinates of the touch point.
</li>
@@ -769,9 +782,9 @@
</p>
<ul>
<li>
- In response to {@link android.view.DragEvent#ACTION_DRAG_ENTERED} or
- {@link android.view.DragEvent#ACTION_DRAG_LOCATION}, the listener can change the appearance
- of the View to indicate that it is about to receive a drop.
+ In response to {@link android.view.DragEvent#ACTION_DRAG_ENTERED}, the listener can change
+ the appearance
+ of the View to indicate that it is ready to receive a drop.
</li>
<li>
An event with the action type {@link android.view.DragEvent#ACTION_DRAG_LOCATION} contains
@@ -784,14 +797,14 @@
<li>
In response to {@link android.view.DragEvent#ACTION_DRAG_EXITED}, the listener should reset
any appearance changes it applied in response to
- {@link android.view.DragEvent#ACTION_DRAG_ENTERED} or
- {@link android.view.DragEvent#ACTION_DRAG_LOCATION}. This indicates to the user that
+ {@link android.view.DragEvent#ACTION_DRAG_ENTERED}. This indicates to the user that
the View is no longer an imminent drop target.
</li>
</ul>
<h3 id="HandleDrop">Responding to a drop</h3>
<p>
- When the user releases the drag shadow on a View in the application, and that View previously
+ When the user releases the drag shadow on a View in the application, but not on its descendant
+ view that can accept the data, and that View previously
reported that it could accept the content being dragged, the system dispatches a drag event
to that View with the action type {@link android.view.DragEvent#ACTION_DROP}. The listener
should do the following:
@@ -800,8 +813,8 @@
<li>
Call {@link android.view.DragEvent#getClipData()} to get the
{@link android.content.ClipData} object that was originally supplied in the call
- to
-{@link android.view.View#startDrag(ClipData, View.DragShadowBuilder, Object, int) startDrag()}
+ to {@link android.view.View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)
+ startDragAndDrop()}
and store it. If the drag and drop operation does not represent data movement,
this may not be necessary.
</li>
@@ -856,9 +869,6 @@
including any case in which the system did not send out a
{@link android.view.DragEvent#ACTION_DROP} event.
</li>
- <li>
- The listener should return boolean <code>true</code> to the system.
- </li>
</ol>
<p>
</p>
diff --git a/docs/html/guide/topics/ui/multi-window.jd b/docs/html/guide/topics/ui/multi-window.jd
index dede557..bab582d 100644
--- a/docs/html/guide/topics/ui/multi-window.jd
+++ b/docs/html/guide/topics/ui/multi-window.jd
@@ -215,7 +215,7 @@
Set this attribute in your manifest's <a href=
"{@docRoot}guide/topics/manifest/activity-element"><code><activity></code></a>
node to indicate whether the activity supports <a href=
- "{@docRoot}training/tv/playback/picture-in-picture.jd">Picture-in-Picture</a>
+ "{@docRoot}training/tv/playback/picture-in-picture.html">Picture-in-Picture</a>
display. This attribute is ignored if <code>android:resizeableActivity</code>
is false.
</p>
diff --git a/docs/html/images/cards/distribute/stories/animoca.jpg b/docs/html/images/cards/distribute/stories/animoca.jpg
new file mode 100644
index 0000000..1886bce
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/animoca.jpg
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/drupe.jpg b/docs/html/images/cards/distribute/stories/drupe.jpg
new file mode 100644
index 0000000..5295695
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/drupe.jpg
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/economist-espresso.png b/docs/html/images/cards/distribute/stories/economist-espresso.png
new file mode 100644
index 0000000..923bf57
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/economist-espresso.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/expressen-sport.png b/docs/html/images/cards/distribute/stories/expressen-sport.png
new file mode 100644
index 0000000..842ed3d
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/expressen-sport.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/glamour.png b/docs/html/images/cards/distribute/stories/glamour.png
new file mode 100644
index 0000000..770b03f
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/glamour.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/happylabs-logo.png b/docs/html/images/cards/distribute/stories/happylabs-logo.png
new file mode 100644
index 0000000..ea20e71
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/happylabs-logo.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/lifesum.png b/docs/html/images/cards/distribute/stories/lifesum.png
new file mode 100644
index 0000000..3975ff2
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/lifesum.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/noom.jpg b/docs/html/images/cards/distribute/stories/noom.jpg
new file mode 100644
index 0000000..dde18a2
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/noom.jpg
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/playlab.jpg b/docs/html/images/cards/distribute/stories/playlab.jpg
new file mode 100644
index 0000000..3b641e6
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/playlab.jpg
Binary files differ
diff --git a/docs/html/images/distribute/stories/animoca-flow.jpg b/docs/html/images/distribute/stories/animoca-flow.jpg
new file mode 100644
index 0000000..d2aa2f6
--- /dev/null
+++ b/docs/html/images/distribute/stories/animoca-flow.jpg
Binary files differ
diff --git a/docs/html/images/distribute/stories/animoca-graph.jpg b/docs/html/images/distribute/stories/animoca-graph.jpg
new file mode 100644
index 0000000..c2a42f4
--- /dev/null
+++ b/docs/html/images/distribute/stories/animoca-graph.jpg
Binary files differ
diff --git a/docs/html/images/distribute/stories/animoca-logo.png b/docs/html/images/distribute/stories/animoca-logo.png
new file mode 100644
index 0000000..4b5b6b5
--- /dev/null
+++ b/docs/html/images/distribute/stories/animoca-logo.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/drupe-icon.png b/docs/html/images/distribute/stories/drupe-icon.png
new file mode 100644
index 0000000..1b75cca
--- /dev/null
+++ b/docs/html/images/distribute/stories/drupe-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/drupe-screenshot.png b/docs/html/images/distribute/stories/drupe-screenshot.png
new file mode 100644
index 0000000..6fd4445
--- /dev/null
+++ b/docs/html/images/distribute/stories/drupe-screenshot.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/economist-espresso-icon.png b/docs/html/images/distribute/stories/economist-espresso-icon.png
new file mode 100644
index 0000000..923bf57
--- /dev/null
+++ b/docs/html/images/distribute/stories/economist-espresso-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/expressen-icon.png b/docs/html/images/distribute/stories/expressen-icon.png
new file mode 100644
index 0000000..4547ce7
--- /dev/null
+++ b/docs/html/images/distribute/stories/expressen-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/glamour-icon.png b/docs/html/images/distribute/stories/glamour-icon.png
new file mode 100644
index 0000000..770b03f
--- /dev/null
+++ b/docs/html/images/distribute/stories/glamour-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/happylabs-happy_pet_icon.png b/docs/html/images/distribute/stories/happylabs-happy_pet_icon.png
new file mode 100644
index 0000000..9b24c4a
--- /dev/null
+++ b/docs/html/images/distribute/stories/happylabs-happy_pet_icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/happylabs-logo.png b/docs/html/images/distribute/stories/happylabs-logo.png
new file mode 100644
index 0000000..ea20e71
--- /dev/null
+++ b/docs/html/images/distribute/stories/happylabs-logo.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/happylabs-variant.png b/docs/html/images/distribute/stories/happylabs-variant.png
new file mode 100644
index 0000000..3ce5342
--- /dev/null
+++ b/docs/html/images/distribute/stories/happylabs-variant.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/lifesum-icon.png b/docs/html/images/distribute/stories/lifesum-icon.png
new file mode 100644
index 0000000..3975ff2
--- /dev/null
+++ b/docs/html/images/distribute/stories/lifesum-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/noom-icon.png b/docs/html/images/distribute/stories/noom-icon.png
new file mode 100644
index 0000000..a853218
--- /dev/null
+++ b/docs/html/images/distribute/stories/noom-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/noom-screenshot.png b/docs/html/images/distribute/stories/noom-screenshot.png
new file mode 100644
index 0000000..0293eea
--- /dev/null
+++ b/docs/html/images/distribute/stories/noom-screenshot.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/playlab-icon.png b/docs/html/images/distribute/stories/playlab-icon.png
new file mode 100644
index 0000000..af80183
--- /dev/null
+++ b/docs/html/images/distribute/stories/playlab-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/playlab-screenshot.png b/docs/html/images/distribute/stories/playlab-screenshot.png
new file mode 100644
index 0000000..42ffb6a
--- /dev/null
+++ b/docs/html/images/distribute/stories/playlab-screenshot.png
Binary files differ
diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js
index f76cf36..1a97db4 100644
--- a/docs/html/jd_extras_en.js
+++ b/docs/html/jd_extras_en.js
@@ -2981,7 +2981,6 @@
"type": "distribute",
"category": "google"
},
-
{
"lang": "en",
"group": "",
@@ -5376,6 +5375,47 @@
"https://storage.googleapis.com/androiddevelopers/shareables/stories/Senri_LeosFortune_gpgs.pdf"
]
},
+ "distribute/stories/games/docs": {
+ "title": "",
+ "resources": [
+ "distribute/stories/games/animoca-star-girl.html",
+ "distribute/stories/games/happy-labs-experiment.html",
+ "distribute/stories/games/playlab-puzzles.html",
+ "distribute/stories/games/upbeat-games.html",
+ "distribute/stories/games/tapps.html",
+ "distribute/stories/games/noodlecake-super-stickman.html",
+ "distribute/stories/games/glu-tap-baseball.html",
+ "distribute/stories/games/doctor-who-legacy.html",
+ "distribute/stories/games/glu-dh.html",
+ "distribute/stories/games/dots.html",
+ "distribute/stories/games/kongregate-adv-cap.html",
+ "distribute/stories/games/kongregate-global-assault.html",
+ "distribute/stories/games/leos-fortune.html",
+ "distribute/stories/games/tiny-co.html",
+ "distribute/stories/games/g4a-indian-rummy.html",
+ "distribute/stories/games/rvappstudios-zombie.html",
+ "distribute/stories/games/glu-eternity-warriors.html",
+ "distribute/stories/games/hotheadgames-firefight.html",
+ "distribute/stories/games/concrete-bowling.html",
+ "distribute/stories/games/gameloft-asphalt8.html"
+ ]
+ },
+ "distribute/stories/apps/docs": {
+ "title": "",
+ "resources": [
+ "distribute/stories/apps/condenast-shopping.html",
+ "distribute/stories/apps/economist-espresso.html",
+ "distribute/stories/apps/expressen-sports.html",
+ "distribute/stories/apps/drupe-communications.html",
+ "distribute/stories/apps/noom-health.html",
+ "distribute/stories/apps/aftenposten.html",
+ "distribute/stories/apps/el-mundo.html",
+ "distribute/stories/apps/segundamano.html",
+ "distribute/stories/apps/remember-the-milk.html",
+ "distribute/stories/apps/intuit-mint.html",
+ "distribute/stories/apps/sayhi.html",
+ ]
+ },
"training/testing/overview": {
"title": "",
"resources": [
diff --git a/docs/html/jd_extras_zh-cn.js b/docs/html/jd_extras_zh-cn.js
index cb1ccb7..866a87e 100644
--- a/docs/html/jd_extras_zh-cn.js
+++ b/docs/html/jd_extras_zh-cn.js
@@ -244,40 +244,40 @@
"overview/zhcn/1": {
"title": "",
"resources": [
- "intl/zh-cn/distribute/essentials/quality/core.html",
- "intl/zh-cn/distribute/essentials/quality/tablets.html",
- "intl/zh-cn/distribute/tools/launch-checklist.html",
- "intl/zh-cn/tools/publishing/publishing_overview.html",
- "intl/zh-cn/distribute/tools/localization-checklist.html"
+ "distribute/essentials/quality/core.html",
+ "distribute/essentials/quality/tablets.html",
+ "distribute/tools/launch-checklist.html",
+ "tools/publishing/publishing_overview.html",
+ "distribute/tools/localization-checklist.html"
]
},
"overview/zhcn/2": {
"title": "",
"resources": [
- "intl/zh-cn/google/play/billing/index.html",
- "intl/zh-cn/google/play/billing/api.html",
- "intl/zh-cn/google/play/billing/billing_admin.html",
- "intl/zh-cn/google/play/billing/billing_testing.html",
- "intl/zh-cn/google/play/billing/billing_best_practices.html"
+ "google/play/billing/index.html",
+ "google/play/billing/api.html",
+ "google/play/billing/billing_admin.html",
+ "google/play/billing/billing_testing.html",
+ "google/play/billing/billing_best_practices.html"
]
},
"overview/zhcn/3": {
"title": "",
"resources": [
"https://play.google.com/intl/en_us/badges/",
- "intl/zh-cn/distribute/tools/promote/device-art.html",
- "intl/zh-cn/distribute/tools/promote/linking.html",
- "intl/zh-cn/distribute/tools/promote/brand.html",
- "intl/zh-cn/tools/help/proguard.html"
+ "distribute/tools/promote/device-art.html",
+ "distribute/tools/promote/linking.html",
+ "distribute/tools/promote/brand.html",
+ "tools/help/proguard.html"
]
},
"overview/zhcn/4": {
"title": "",
"resources": [
- "intl/zh-cn/design/style/writing.html",
- "intl/zh-cn/training/basics/fragments/fragment-ui.html",
- "intl/zh-cn/training/multiscreen/index.html",
- "intl/zh-cn/training/monitoring-device-state/index.html"
+ "design/style/writing.html",
+ "training/basics/fragments/fragment-ui.html",
+ "training/multiscreen/index.html",
+ "training/monitoring-device-state/index.html"
]
},
"overview/carousel/zhcn": {
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/sdk/support_api_diff/22.2.0/changes/alldiffs_index_additions.html b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_additions.html
index 9786c92..fcff8ea 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_additions.html
@@ -332,7 +332,7 @@
<!-- Method zBy -->
<nobr><A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html#android.support.v4.view.ViewPropertyAnimatorCompat.zBy_added(float)" class="hiddenlink" target="rightframe"><b>zBy</b>
(<code>float</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_all.html b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_all.html
index 7055d15..4e923e6 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_all.html
@@ -425,7 +425,7 @@
<!-- Method zBy -->
<nobr><A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html#android.support.v4.view.ViewPropertyAnimatorCompat.zBy_added(float)" class="hiddenlink" target="rightframe"><b>zBy</b>
(<code>float</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_changes.html b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_changes.html
index c40c9f5..4002f63 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_changes.html
@@ -114,7 +114,7 @@
<A HREF="android.support.v4.view.ViewParentCompat.html" class="hiddenlink" target="rightframe">ViewParentCompat</A><br>
<!-- Class ViewPropertyAnimatorCompat -->
<A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html" class="hiddenlink" target="rightframe">ViewPropertyAnimatorCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_removals.html b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_removals.html
index 68d2c20..c6ec566 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/alldiffs_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.Builder.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.Builder.html
index ae6415e..42084ee 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.Builder.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.Builder.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.CustomAction.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.CustomAction.html
index 4e476b3..deab0af 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.CustomAction.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.CustomAction.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html
index bd3e5dd..16b5462 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html
@@ -144,7 +144,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewCompat.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewCompat.html
index 1574050..d48ce71 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewCompat.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewCompat.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPager.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPager.html
index f03c403..bed9612 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPager.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPager.html
@@ -140,7 +140,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewParentCompat.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewParentCompat.html
index 6cdd299..08d2ab2 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewParentCompat.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewParentCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html
index 5706997..1bfa2b3 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
index e746f08..09b19c2 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
@@ -151,7 +151,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatActivity.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatActivity.html
index ccce9c5..7b2f8bb 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatActivity.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatActivity.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatCallback.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatCallback.html
index f53a519..fd75b74 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatCallback.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatCallback.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDelegate.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDelegate.html
index 88490d4..5df87b9 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDelegate.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDelegate.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDialog.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDialog.html
index 42d29a8..6d992ad 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDialog.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.app.AppCompatDialog.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.util.SortedList.html b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.util.SortedList.html
index 0364519..edfb8bd 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.util.SortedList.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/android.support.v7.util.SortedList.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/changes-summary.html b/docs/html/sdk/support_api_diff/22.2.0/changes/changes-summary.html
index c6736cd..72ddc5a 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/changes-summary.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/changes-summary.html
@@ -233,7 +233,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_additions.html b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_additions.html
index ccd5b66..4537739 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_additions.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_all.html b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_all.html
index 728e37d..50a77aa 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_all.html
@@ -88,7 +88,7 @@
<A HREF="android.support.v4.view.ViewPager.html" class="hiddenlink" target="rightframe">ViewPager</A><br>
<A HREF="android.support.v4.view.ViewParentCompat.html" class="hiddenlink" target="rightframe">ViewParentCompat</A><br>
<A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html" class="hiddenlink" target="rightframe">ViewPropertyAnimatorCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_changes.html b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_changes.html
index 64e7f44..04fc9bc 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_changes.html
@@ -88,7 +88,7 @@
<A HREF="android.support.v4.view.ViewPager.html" class="hiddenlink" target="rightframe">ViewPager</A><br>
<A HREF="android.support.v4.view.ViewParentCompat.html" class="hiddenlink" target="rightframe">ViewParentCompat</A><br>
<A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html" class="hiddenlink" target="rightframe">ViewPropertyAnimatorCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_removals.html b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_removals.html
index 792fc4e..8881d7d 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/classes_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_additions.html b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_additions.html
index 3237ba3..603d376 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_additions.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_all.html b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_all.html
index 637582e..a29ef64 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_all.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_changes.html b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_changes.html
index 728fa2d..182ad3c 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_removals.html b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_removals.html
index 1b95544..9f9bb438 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/constructors_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_additions.html b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_additions.html
index 48de992..6b0d997 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_additions.html
@@ -69,7 +69,7 @@
</nobr><br>
<nobr><A HREF="android.support.v4.media.session.PlaybackStateCompat.html#android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM" class="hiddenlink" target="rightframe">STATE_SKIPPING_TO_QUEUE_ITEM</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_all.html b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_all.html
index 02f6dc0..726deb6 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_all.html
@@ -69,7 +69,7 @@
</nobr><br>
<nobr><A HREF="android.support.v4.media.session.PlaybackStateCompat.html#android.support.v4.media.session.PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM" class="hiddenlink" target="rightframe">STATE_SKIPPING_TO_QUEUE_ITEM</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_changes.html b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_changes.html
index 4ebfa31..e4376a5 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_removals.html b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_removals.html
index 09b0726..db6d007 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/fields_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_help.html b/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_help.html
index 8bfbd1d..e978be0 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_help.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_help.html
@@ -120,7 +120,7 @@
There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass.
In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes.
</BLOCKQUOTE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_statistics.html b/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_statistics.html
index 229819b..6a96e39 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_statistics.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_statistics.html
@@ -265,7 +265,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_topleftframe.html b/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_topleftframe.html
index 36f9836..6a2e76e 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_topleftframe.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/jdiff_topleftframe.html
@@ -49,7 +49,7 @@
<TD NOWRAP><FONT CLASS="indexText" size="-2"><A HREF="fields_index_all.html" TARGET="bottomleftframe">By Field</A></FONT><br></TD>
</TR>
</TABLE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_additions.html b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_additions.html
index f7c8488..b7b94ac 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_additions.html
@@ -264,7 +264,7 @@
(<code>float</code>)</A></nobr><br>
<nobr><A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html#android.support.v4.view.ViewPropertyAnimatorCompat.zBy_added(float)" class="hiddenlink" target="rightframe"><b>zBy</b>
(<code>float</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_all.html b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_all.html
index 63a2848..04d38841 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_all.html
@@ -266,7 +266,7 @@
(<code>float</code>)</A></nobr><br>
<nobr><A HREF="android.support.v4.view.ViewPropertyAnimatorCompat.html#android.support.v4.view.ViewPropertyAnimatorCompat.zBy_added(float)" class="hiddenlink" target="rightframe"><b>zBy</b>
(<code>float</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_changes.html b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_changes.html
index 3e43f87..3bfd471 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_changes.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.view.ViewPager.html#android.support.v4.view.ViewPager.setOnPageChangeListener_changed(android.support.v4.view.ViewPager.OnPageChangeListener)" class="hiddenlink" target="rightframe">setOnPageChangeListener
(<code>OnPageChangeListener</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_removals.html b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_removals.html
index b5aea4f..e85e6fc 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/methods_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_additions.html b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_additions.html
index 7d92a82..183d500 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_additions.html
@@ -61,7 +61,7 @@
<A HREF="changes-summary.html#android.support.v7.appcompat" class="hiddenlink" target="rightframe"><b>android.support.v7.appcompat</b></A><br>
<A HREF="changes-summary.html#android.support.v7.recyclerview" class="hiddenlink" target="rightframe"><b>android.support.v7.recyclerview</b></A><br>
<A HREF="changes-summary.html#android.support.v7.widget.helper" class="hiddenlink" target="rightframe"><b>android.support.v7.widget.helper</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_all.html b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_all.html
index 2735fe9..0fb83cf 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_all.html
@@ -66,7 +66,7 @@
<A HREF="changes-summary.html#android.support.v7.recyclerview" class="hiddenlink" target="rightframe"><b>android.support.v7.recyclerview</b></A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
<A HREF="changes-summary.html#android.support.v7.widget.helper" class="hiddenlink" target="rightframe"><b>android.support.v7.widget.helper</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_changes.html b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_changes.html
index 3e40878..917e060 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_changes.html
@@ -55,7 +55,7 @@
<A HREF="pkg_android.support.v4.view.accessibility.html" class="hiddenlink" target="rightframe">android.support.v4.view.accessibility</A><br>
<A HREF="pkg_android.support.v7.app.html" class="hiddenlink" target="rightframe">android.support.v7.app</A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_removals.html b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_removals.html
index d0ffabc..d5a825d 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/packages_index_removals.html
@@ -49,7 +49,7 @@
</div>
<br>
<div id="indexTableEntries">
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.media.session.html b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.media.session.html
index 4dfd7de..992f05d 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.media.session.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.media.session.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.accessibility.html b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.accessibility.html
index f52efcb..86dc841 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.accessibility.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.accessibility.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.html b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.html
index f05d812..ad51e62 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v4.view.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.app.html b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.app.html
index 44bf61d..508323f 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.app.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.app.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.util.html b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.util.html
index d08d9af..8f74fb7 100644
--- a/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.util.html
+++ b/docs/html/sdk/support_api_diff/22.2.0/changes/pkg_android.support.v7.util.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_additions.html b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_additions.html
index 3374ffd..a1055f1 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_additions.html
@@ -660,7 +660,7 @@
<!-- Method useStaticShadow -->
<nobr><A HREF="android.support.v17.leanback.widget.ShadowOverlayContainer.html#android.support.v17.leanback.widget.ShadowOverlayContainer.useStaticShadow_added()" class="hiddenlink" target="rightframe"><b>useStaticShadow</b>
()</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_all.html b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_all.html
index bb24c29..48f43d2 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_all.html
@@ -913,7 +913,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html" class="hiddenlink" target="rightframe">VerticalGridPresenter</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_changes.html b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_changes.html
index 8340749..d5b60c9 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_changes.html
@@ -389,7 +389,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html" class="hiddenlink" target="rightframe">VerticalGridPresenter</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_removals.html b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_removals.html
index 0d670c0..f2a8797 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/alldiffs_index_removals.html
@@ -83,7 +83,7 @@
<!-- Field Platform_V12_AppCompat_Light -->
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Platform_V12_AppCompat_Light" class="hiddenlink" target="rightframe"><strike>Platform_V12_AppCompat_Light</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.CoordinatorLayout.Behavior.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
index 78392f4..c45efa2 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.Behavior.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.Behavior.html
index 6d68976..a23105a 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.Behavior.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.Behavior.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.html
index 21b55c0..91a6d77 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.FloatingActionButton.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.Snackbar.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.Snackbar.html
index 028df73..24fdf70 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.Snackbar.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.Snackbar.html
@@ -130,7 +130,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.Tab.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.Tab.html
index 2acf995..74a296d 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.Tab.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.Tab.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.html
index 22a5ff5..3a8d151 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.design.widget.TabLayout.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsFragment.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsFragment.html
index 16e5798..7ae6ce1 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsFragment.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsFragment.html
@@ -136,7 +136,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsSupportFragment.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
index 7825292..d1733ac 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
@@ -136,7 +136,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.DetailsOverviewRowPresenter.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.DetailsOverviewRowPresenter.html
index ba8e6af..799db3d 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.DetailsOverviewRowPresenter.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.DetailsOverviewRowPresenter.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder.html
index 25f1b48..f8c0cbf 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.html
index a88f694..b98c33e 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ItemBridgeAdapter.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ListRowPresenter.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ListRowPresenter.html
index 5d71011..f9ad877 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ListRowPresenter.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ListRowPresenter.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.OnChildSelectedListener.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.OnChildSelectedListener.html
index eb8a563..f13b147 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.OnChildSelectedListener.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.OnChildSelectedListener.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.ViewHolder.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.ViewHolder.html
index a94d9f5..55f3575 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.ViewHolder.html
@@ -116,7 +116,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.html
index fde5e4b..417bf86 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.Presenter.html
@@ -116,7 +116,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.PresenterSelector.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.PresenterSelector.html
index 06a0a79..827b2cf 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.PresenterSelector.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.PresenterSelector.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.RowPresenter.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.RowPresenter.html
index 6da5f9b..24b7683 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.RowPresenter.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.RowPresenter.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html
index ac3ac3a..9814065 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html
@@ -172,7 +172,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html
index ab9ce07..c18075e 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompat.Action.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompat.Action.html
index 8519610..d96b874 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompat.Action.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompat.Action.html
@@ -131,7 +131,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.Action.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.Action.html
index 498374e..09ae7fc 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.Action.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.Action.html
@@ -151,7 +151,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.html
index 309d7b6..9824574 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v4.app.NotificationCompatBase.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.attr.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.attr.html
index 229289c..541f134 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.attr.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.attr.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.dimen.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.dimen.html
index a253e59..584073e 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.dimen.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.dimen.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.drawable.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.drawable.html
index 4f87127..45a1dc7 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.drawable.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.drawable.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.id.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.id.html
index e856c8b..cecd912 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.id.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.id.html
@@ -192,7 +192,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.integer.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.integer.html
index 70585a7..4b90739 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.integer.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.integer.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.layout.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.layout.html
index 8130707..c0a1304 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.layout.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.layout.html
@@ -157,7 +157,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.string.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.string.html
index 6a01b20..39be2b2 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.string.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.string.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.style.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.style.html
index 886f2ce..ffc3690 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.style.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.style.html
@@ -186,7 +186,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.styleable.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.styleable.html
index cd9cd60..713526b 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.styleable.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.appcompat.R.styleable.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.util.SortedList.html b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.util.SortedList.html
index b38c632..181a0fc 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.util.SortedList.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/android.support.v7.util.SortedList.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/changes-summary.html b/docs/html/sdk/support_api_diff/22.2.1/changes/changes-summary.html
index e05b67d..b500246 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/changes-summary.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/changes-summary.html
@@ -177,7 +177,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_additions.html b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_additions.html
index 52b4ab7..f2fe6ec 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_additions.html
@@ -103,7 +103,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="pkg_android.support.v17.leanback.widget.html#OnChildViewHolderSelectedListener" class="hiddenlink" target="rightframe"><b>OnChildViewHolderSelectedListener</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_all.html b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_all.html
index 1edab7a..16ebdfa 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_all.html
@@ -339,7 +339,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html" class="hiddenlink" target="rightframe">VerticalGridPresenter</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_changes.html b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_changes.html
index f46bc67..8072f0f 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_changes.html
@@ -259,7 +259,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html" class="hiddenlink" target="rightframe">VerticalGridPresenter</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_removals.html b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_removals.html
index 09c0d19..757f5bb 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/classes_index_removals.html
@@ -63,7 +63,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="pkg_android.support.v17.leanback.widget.html#GridLayoutManager" class="hiddenlink" target="rightframe"><strike>GridLayoutManager</strike></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_additions.html b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_additions.html
index e694216..3bced29 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_additions.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.app.NotificationCompatBase.html#android.support.v4.app.NotificationCompatBase.ctor_added()" class="hiddenlink" target="rightframe"><b>NotificationCompatBase</b>
()</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_all.html b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_all.html
index 27d7557..7769d75 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_all.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.app.NotificationCompatBase.html#android.support.v4.app.NotificationCompatBase.ctor_added()" class="hiddenlink" target="rightframe"><b>NotificationCompatBase</b>
()</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_changes.html b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_changes.html
index a5ca2ef..122cdce 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_removals.html b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_removals.html
index 74a09ba..dadd1cd 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/constructors_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_additions.html b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_additions.html
index 2ae5237..5d6411a 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_additions.html
@@ -263,7 +263,7 @@
</nobr><br>
<nobr><A HREF="android.support.v7.appcompat.R.id.html#android.support.v7.appcompat.R.id.time" class="hiddenlink" target="rightframe">time</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_all.html b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_all.html
index 4a9194d..85420c3 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_all.html
@@ -291,7 +291,7 @@
</nobr><br>
<nobr><A HREF="android.support.v7.appcompat.R.id.html#android.support.v7.appcompat.R.id.time" class="hiddenlink" target="rightframe">time</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_changes.html b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_changes.html
index 0e2ccff..301881a 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_removals.html b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_removals.html
index 6245b05..b1bf691 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/fields_index_removals.html
@@ -55,7 +55,7 @@
</nobr><br>
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Platform_V12_AppCompat_Light" class="hiddenlink" target="rightframe"><strike>Platform_V12_AppCompat_Light</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_help.html b/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_help.html
index 25acfbe..f9c1f08 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_help.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_help.html
@@ -120,7 +120,7 @@
There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass.
In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes.
</BLOCKQUOTE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_statistics.html b/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_statistics.html
index 0f4805f..322ad44 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_statistics.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_statistics.html
@@ -368,7 +368,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_topleftframe.html b/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_topleftframe.html
index 36f9836..6a2e76e 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_topleftframe.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/jdiff_topleftframe.html
@@ -49,7 +49,7 @@
<TD NOWRAP><FONT CLASS="indexText" size="-2"><A HREF="fields_index_all.html" TARGET="bottomleftframe">By Field</A></FONT><br></TD>
</TR>
</TABLE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_additions.html b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_additions.html
index 33a155f..5a237de 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_additions.html
@@ -224,7 +224,7 @@
</A></nobr><br>
<nobr><A HREF="android.support.v17.leanback.widget.ShadowOverlayContainer.html#android.support.v17.leanback.widget.ShadowOverlayContainer.useStaticShadow_added()" class="hiddenlink" target="rightframe"><b>useStaticShadow</b>
()</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_all.html b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_all.html
index b9cf858..014ef9a 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_all.html
@@ -256,7 +256,7 @@
</A></nobr><br>
<nobr><A HREF="android.support.v17.leanback.widget.ShadowOverlayContainer.html#android.support.v17.leanback.widget.ShadowOverlayContainer.useStaticShadow_added()" class="hiddenlink" target="rightframe"><b>useStaticShadow</b>
()</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_changes.html b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_changes.html
index 4e2d329..6b02e2a 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_changes.html
@@ -89,7 +89,7 @@
<nobr><A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html#android.support.v17.leanback.widget.VerticalGridPresenter.isUsingZOrder_changed(android.content.Context)" class="hiddenlink" target="rightframe">type
(<code>Context</code>) in android.support.v17.leanback.widget.VerticalGridPresenter
</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_removals.html b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_removals.html
index b5aea4f..e85e6fc 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/methods_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_additions.html b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_additions.html
index 6311752..d08c85c 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_additions.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_additions.html
@@ -51,7 +51,7 @@
<div id="indexTableEntries">
<A NAME="A"></A>
<A HREF="changes-summary.html#android.support.v17.leanback.system" class="hiddenlink" target="rightframe"><b>android.support.v17.leanback.system</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_all.html b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_all.html
index 3f5ee13..712eac9 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_all.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_all.html
@@ -58,7 +58,7 @@
<A HREF="pkg_android.support.v7.app.html" class="hiddenlink" target="rightframe">android.support.v7.app</A><br>
<A HREF="pkg_android.support.v7.appcompat.html" class="hiddenlink" target="rightframe">android.support.v7.appcompat</A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_changes.html b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_changes.html
index a4637a7..e53ee87 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_changes.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_changes.html
@@ -57,7 +57,7 @@
<A HREF="pkg_android.support.v7.app.html" class="hiddenlink" target="rightframe">android.support.v7.app</A><br>
<A HREF="pkg_android.support.v7.appcompat.html" class="hiddenlink" target="rightframe">android.support.v7.appcompat</A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_removals.html b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_removals.html
index d0ffabc..d5a825d 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_removals.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/packages_index_removals.html
@@ -49,7 +49,7 @@
</div>
<br>
<div id="indexTableEntries">
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.design.widget.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.design.widget.html
index 345f33b..25070ce 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.design.widget.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.design.widget.html
@@ -140,7 +140,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.app.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.app.html
index 873bc6b..48f868b 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.app.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.app.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.widget.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.widget.html
index 7197d3a..ac3d8435 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.widget.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v17.leanback.widget.html
@@ -324,7 +324,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v4.app.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v4.app.html
index 1d15399..3122461 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v4.app.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v4.app.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.app.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.app.html
index 47c0894..5f69310 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.app.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.app.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.appcompat.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.appcompat.html
index 4c562aec..00bcf19 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.appcompat.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.appcompat.html
@@ -161,7 +161,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.util.html b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.util.html
index bac0313..e0f4bcd 100644
--- a/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.util.html
+++ b/docs/html/sdk/support_api_diff/22.2.1/changes/pkg_android.support.v7.util.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_additions.html b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_additions.html
index 5495f89..1717f7d 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_additions.html
@@ -1104,7 +1104,7 @@
<!-- Field Widget_AppCompat_SeekBar -->
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_SeekBar" class="hiddenlink" target="rightframe">Widget_AppCompat_SeekBar</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_all.html b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_all.html
index 575ee95..d4c30e3 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_all.html
@@ -1511,7 +1511,7 @@
<!-- Field Widget_AppCompat_SeekBar -->
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_SeekBar" class="hiddenlink" target="rightframe">Widget_AppCompat_SeekBar</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_changes.html b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_changes.html
index d7ebd36..6562fa8 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_changes.html
@@ -548,7 +548,7 @@
<A HREF="android.support.v17.leanback.app.VerticalGridSupportFragment.html" class="hiddenlink" target="rightframe">VerticalGridSupportFragment</A><br>
<!-- Class ViewCompat -->
<A HREF="android.support.v4.view.ViewCompat.html" class="hiddenlink" target="rightframe">ViewCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_removals.html b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_removals.html
index 0d7da0f..9b21b1d 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/alldiffs_index_removals.html
@@ -270,7 +270,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v17.leanback.app.GuidedStepFragment.html#android.support.v17.leanback.app.GuidedStepFragment.setEntryTransitionEnabled_removed(boolean)" class="hiddenlink" target="rightframe"><strike>setEntryTransitionEnabled</strike>
(<code>boolean</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsCallback.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsCallback.html
index 0466d5d..6ba5f831 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsCallback.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsCallback.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
index e3aefb5..c53722b 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.html
index 9312755..db59258 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsIntent.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsService.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsService.html
index 2a0fd18..fd5a12b 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsService.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsService.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSession.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSession.html
index 6b70224..6910bab 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSession.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSession.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSessionToken.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSessionToken.html
index 2bdb42e..0282d49 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSessionToken.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.customtabs.CustomTabsSessionToken.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.SavedState.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.SavedState.html
index bb89bbd..cea16c0 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.SavedState.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.SavedState.html
@@ -123,7 +123,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.html
index b538999a..672951d 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.Behavior.html
@@ -131,7 +131,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.LayoutParams.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.LayoutParams.html
index 6d19d36..0218c30 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.LayoutParams.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.LayoutParams.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
index c089db3..424a618 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
@@ -124,7 +124,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CollapsingToolbarLayout.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
index 49d92737..8e708c0 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
@@ -143,7 +143,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CoordinatorLayout.SavedState.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CoordinatorLayout.SavedState.html
index f92babd..496a460 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CoordinatorLayout.SavedState.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.CoordinatorLayout.SavedState.html
@@ -123,7 +123,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html
index 07fdda1..eba4247 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.html
index 90553fa..520fdcf 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.FloatingActionButton.html
@@ -116,7 +116,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.NavigationView.SavedState.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.NavigationView.SavedState.html
index 8a9ae80..61428ca 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.NavigationView.SavedState.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.NavigationView.SavedState.html
@@ -123,7 +123,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.Snackbar.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.Snackbar.html
index d741863..f33e47a 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.Snackbar.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.Snackbar.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.TextInputLayout.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.TextInputLayout.html
index 30d0dd6..609e864 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.TextInputLayout.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.design.widget.TextInputLayout.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html
index b6f2e48..967542f 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v14.preference.EditTextPreferenceDialogFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v14.preference.EditTextPreferenceDialogFragment.html
index 30f22a8..6b6151b 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v14.preference.EditTextPreferenceDialogFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v14.preference.EditTextPreferenceDialogFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseFragment.html
index f4d77bf..14f6fdfa 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseFragment.html
@@ -150,7 +150,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html
index a48ea3b..7adcbe8 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html
@@ -150,7 +150,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsFragment.html
index d38127c..2240f4e 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsFragment.html
@@ -143,7 +143,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
index a02b8f3..23ca151 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
@@ -143,7 +143,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html
index 725281a..4c78b30 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html
@@ -215,7 +215,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlayFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlayFragment.html
index 71e230b..ecb35bef 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlayFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlayFragment.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlaySupportFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlaySupportFragment.html
index a794f14..1809374 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlaySupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.PlaybackOverlaySupportFragment.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchFragment.html
index 827f46b..2c444ae 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchSupportFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchSupportFragment.html
index f1e2826..7ff0204 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.SearchSupportFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridFragment.html
index 01bb4de..5516bda 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridFragment.html
@@ -137,7 +137,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridSupportFragment.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridSupportFragment.html
index a818a79..0f09ec0 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.app.VerticalGridSupportFragment.html
@@ -154,7 +154,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.FragmentAnimationProvider.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.FragmentAnimationProvider.html
index 1cf004d..e457402 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.FragmentAnimationProvider.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.FragmentAnimationProvider.html
@@ -165,7 +165,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html
index e0b94bc..9794929 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html
@@ -165,7 +165,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html
index 96d30a4..3408bd9 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.html
index 84b93bf..857b7d8 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedAction.html
@@ -143,7 +143,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html
index 38e83b4..497e985 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html
index 104f325..cd45d6d 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html
@@ -165,7 +165,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ImageCardView.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ImageCardView.html
index 1535ca0..a5aa07e 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ImageCardView.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ImageCardView.html
@@ -151,7 +151,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ListRowPresenter.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ListRowPresenter.html
index f536d92..11b95c40 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ListRowPresenter.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ListRowPresenter.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.RowPresenter.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.RowPresenter.html
index 3f10b29..0f254d7 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.RowPresenter.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.RowPresenter.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.SearchBar.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.SearchBar.html
index 2965c15..6e4b41c 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.SearchBar.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.SearchBar.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html
index 761fd92..553bbb1 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.ShadowOverlayContainer.html
@@ -127,7 +127,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html
index e999838..f1d6fb6 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v17.leanback.widget.VerticalGridPresenter.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.app.FragmentActivity.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.app.FragmentActivity.html
index c68356c..dd88aca 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.app.FragmentActivity.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.app.FragmentActivity.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.content.res.ResourcesCompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.content.res.ResourcesCompat.html
index 9bdd4f7..bb339da 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.content.res.ResourcesCompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.content.res.ResourcesCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.media.session.MediaSessionCompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.media.session.MediaSessionCompat.html
index 5b5f1e3..278f42d 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.media.session.MediaSessionCompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.media.session.MediaSessionCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.view.ViewCompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.view.ViewCompat.html
index 2e39745..03dda58 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.view.ViewCompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.view.ViewCompat.html
@@ -179,7 +179,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.NestedScrollView.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.NestedScrollView.html
index 8d4cbaa..cdb3d13 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.NestedScrollView.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.NestedScrollView.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.ScrollerCompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.ScrollerCompat.html
index 7f47eae..9ef847e 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.ScrollerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.ScrollerCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.TextViewCompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.TextViewCompat.html
index 3641625..87b1da2 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.TextViewCompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v4.widget.TextViewCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.attr.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.attr.html
index dc6fce2..ef31519 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.attr.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.attr.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.bool.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.bool.html
index 973cf83..c26367a 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.bool.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.bool.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.dimen.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.dimen.html
index ca5fec2..bd324cb 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.dimen.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.dimen.html
@@ -186,7 +186,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.drawable.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.drawable.html
index 3cfba65..c9264d6 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.drawable.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.drawable.html
@@ -157,7 +157,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.id.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.id.html
index a25139d..e7f9f95 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.id.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.id.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.layout.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.layout.html
index 0b485ea..c04d4f7 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.layout.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.layout.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.string.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.string.html
index b5b32c7..9dc84ab 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.string.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.string.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.style.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.style.html
index a7b4e2d..58f9025 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.style.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.style.html
@@ -158,7 +158,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.styleable.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.styleable.html
index 3f6d157..49adae8 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.styleable.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.appcompat.R.styleable.html
@@ -150,7 +150,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.Palette.Builder.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.Palette.Builder.html
index 03171fb..22ee483 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.Palette.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.Palette.Builder.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.drawable.DrawerArrowDrawable.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.drawable.DrawerArrowDrawable.html
index ec3cb15..77232ba 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.drawable.DrawerArrowDrawable.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.graphics.drawable.DrawerArrowDrawable.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html
index aec06c0..5cac372 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html
@@ -154,7 +154,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.html
index 381eb20..437c56d 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouteDescriptor.html
@@ -147,7 +147,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouter.RouteInfo.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouter.RouteInfo.html
index 239341d..51afa4f 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouter.RouteInfo.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.media.MediaRouter.RouteInfo.html
@@ -166,7 +166,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.preference.EditTextPreferenceDialogFragmentCompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.preference.EditTextPreferenceDialogFragmentCompat.html
index 05b65ff..ac63d53 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.preference.EditTextPreferenceDialogFragmentCompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/android.support.v7.preference.EditTextPreferenceDialogFragmentCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/changes-summary.html b/docs/html/sdk/support_api_diff/23.1.0/changes/changes-summary.html
index 728ade1..d9658ba 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/changes-summary.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/changes-summary.html
@@ -232,7 +232,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_additions.html b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_additions.html
index c0022c9..1a97c98 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_additions.html
@@ -197,7 +197,7 @@
<A HREF="pkg_android.support.v17.leanback.widget.html#ShadowOverlayHelper" class="hiddenlink" target="rightframe"><b>ShadowOverlayHelper</b></A><br>
<A HREF="pkg_android.support.v17.leanback.widget.html#ShadowOverlayHelper.Builder" class="hiddenlink" target="rightframe"><b>ShadowOverlayHelper.Builder</b></A><br>
<A HREF="pkg_android.support.v17.leanback.widget.html#ShadowOverlayHelper.Options" class="hiddenlink" target="rightframe"><b>ShadowOverlayHelper.Options</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_all.html b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_all.html
index 05f83b8..c8f86f4 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_all.html
@@ -471,7 +471,7 @@
<A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html" class="hiddenlink" target="rightframe">VerticalGridPresenter</A><br>
<A HREF="android.support.v17.leanback.app.VerticalGridSupportFragment.html" class="hiddenlink" target="rightframe">VerticalGridSupportFragment</A><br>
<A HREF="android.support.v4.view.ViewCompat.html" class="hiddenlink" target="rightframe">ViewCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_changes.html b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_changes.html
index b1ee972..f280906 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_changes.html
@@ -415,7 +415,7 @@
<A HREF="android.support.v17.leanback.widget.VerticalGridPresenter.html" class="hiddenlink" target="rightframe">VerticalGridPresenter</A><br>
<A HREF="android.support.v17.leanback.app.VerticalGridSupportFragment.html" class="hiddenlink" target="rightframe">VerticalGridSupportFragment</A><br>
<A HREF="android.support.v4.view.ViewCompat.html" class="hiddenlink" target="rightframe">ViewCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_removals.html b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_removals.html
index e6da73f..c466298 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/classes_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_additions.html b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_additions.html
index 3bf8178..45f72f1 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_additions.html
@@ -97,7 +97,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.design.widget.NavigationView.SavedState.html#android.support.design.widget.NavigationView.SavedState.ctor_added(android.os.Parcel, java.lang.ClassLoader)" class="hiddenlink" target="rightframe"><b>NavigationView.SavedState</b>
(<code>Parcel, ClassLoader</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_all.html b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_all.html
index 546eae8..8dda68f 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_all.html
@@ -106,7 +106,7 @@
(<code>Parcel</code>)</A></nobr> constructor<br>
<nobr><A HREF="android.support.design.widget.NavigationView.SavedState.html#android.support.design.widget.NavigationView.SavedState.ctor_added(android.os.Parcel, java.lang.ClassLoader)" class="hiddenlink" target="rightframe"><b>NavigationView.SavedState</b>
(<code>Parcel, ClassLoader</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_changes.html b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_changes.html
index 52c8b49..d6584d1 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_removals.html b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_removals.html
index 337274d..49ee23c 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/constructors_index_removals.html
@@ -71,7 +71,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.design.widget.NavigationView.SavedState.html#android.support.design.widget.NavigationView.SavedState.ctor_removed(android.os.Parcel)" class="hiddenlink" target="rightframe"><strike>NavigationView.SavedState</strike>
(<code>Parcel</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_additions.html b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_additions.html
index d1ef3ac..0c43320 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_additions.html
@@ -329,7 +329,7 @@
</nobr><br>
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_SeekBar" class="hiddenlink" target="rightframe">Widget_AppCompat_SeekBar</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_all.html b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_all.html
index 697f16a..71d9b71 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_all.html
@@ -339,7 +339,7 @@
</nobr><br>
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_SeekBar" class="hiddenlink" target="rightframe">Widget_AppCompat_SeekBar</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_changes.html b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_changes.html
index 0e2ccff..301881a 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_removals.html b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_removals.html
index 93bcdc3..f520de6 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/fields_index_removals.html
@@ -67,7 +67,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.RtlOverlay_Widget_AppCompat_ActionButton_Overflow" class="hiddenlink" target="rightframe"><strike>RtlOverlay_Widget_AppCompat_ActionButton_Overflow</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_help.html b/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_help.html
index b03b763..0ba76e7 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_help.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_help.html
@@ -120,7 +120,7 @@
There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass.
In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes.
</BLOCKQUOTE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_statistics.html b/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_statistics.html
index bdda25f..ac4d43e 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_statistics.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_statistics.html
@@ -568,7 +568,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_topleftframe.html b/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_topleftframe.html
index 36f9836..6a2e76e 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_topleftframe.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/jdiff_topleftframe.html
@@ -49,7 +49,7 @@
<TD NOWRAP><FONT CLASS="indexText" size="-2"><A HREF="fields_index_all.html" TARGET="bottomleftframe">By Field</A></FONT><br></TD>
</TR>
</TABLE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_additions.html b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_additions.html
index 9b4eab3..fee36df 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_additions.html
@@ -516,7 +516,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.customtabs.CustomTabsService.html#android.support.customtabs.CustomTabsService.updateVisuals_added(android.support.customtabs.CustomTabsSessionToken, android.os.Bundle)" class="hiddenlink" target="rightframe"><b>updateVisuals</b>
(<code>CustomTabsSessionToken, Bundle</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_all.html b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_all.html
index d63656c..7a2fe5c 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_all.html
@@ -616,7 +616,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.customtabs.CustomTabsService.html#android.support.customtabs.CustomTabsService.updateVisuals_added(android.support.customtabs.CustomTabsSessionToken, android.os.Bundle)" class="hiddenlink" target="rightframe"><b>updateVisuals</b>
(<code>CustomTabsSessionToken, Bundle</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_changes.html b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_changes.html
index 8271b35..254a721 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_changes.html
@@ -77,7 +77,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.media.MediaRouteDescriptor.Builder.html#android.support.v7.media.MediaRouteDescriptor.Builder.setConnecting_changed(boolean)" class="hiddenlink" target="rightframe">setConnecting
(<code>boolean</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_removals.html b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_removals.html
index 7250505..0b6f2b4 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/methods_index_removals.html
@@ -158,7 +158,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v17.leanback.app.GuidedStepFragment.html#android.support.v17.leanback.app.GuidedStepFragment.setEntryTransitionEnabled_removed(boolean)" class="hiddenlink" target="rightframe"><strike>setEntryTransitionEnabled</strike>
(<code>boolean</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_additions.html b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_additions.html
index 1776064..e7ed63e 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_additions.html
@@ -49,7 +49,7 @@
</div>
<br>
<div id="indexTableEntries">
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_all.html b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_all.html
index 2bf0974..4236847 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_all.html
@@ -67,7 +67,7 @@
<A HREF="pkg_android.support.v7.graphics.drawable.html" class="hiddenlink" target="rightframe">android.support.v7.graphics.drawable</A><br>
<A HREF="pkg_android.support.v7.media.html" class="hiddenlink" target="rightframe">android.support.v7.media</A><br>
<A HREF="pkg_android.support.v7.preference.html" class="hiddenlink" target="rightframe">android.support.v7.preference</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_changes.html b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_changes.html
index 519e9aa..8d51ea2 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_changes.html
@@ -67,7 +67,7 @@
<A HREF="pkg_android.support.v7.graphics.drawable.html" class="hiddenlink" target="rightframe">android.support.v7.graphics.drawable</A><br>
<A HREF="pkg_android.support.v7.media.html" class="hiddenlink" target="rightframe">android.support.v7.media</A><br>
<A HREF="pkg_android.support.v7.preference.html" class="hiddenlink" target="rightframe">android.support.v7.preference</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_removals.html b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_removals.html
index 9fd0f7e..ae935f4 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/packages_index_removals.html
@@ -49,7 +49,7 @@
</div>
<br>
<div id="indexTableEntries">
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.customtabs.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.customtabs.html
index d85f3f8..ea62004 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.customtabs.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.customtabs.html
@@ -140,7 +140,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.design.widget.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.design.widget.html
index 449afa6..45625b9 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.design.widget.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.design.widget.html
@@ -211,7 +211,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.percent.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.percent.html
index 53b92a9..13b9732 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.percent.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.percent.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v14.preference.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v14.preference.html
index 3ef0a0f..7364b7c 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v14.preference.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v14.preference.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.app.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.app.html
index 49ee52f..21763fb 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.app.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.app.html
@@ -211,7 +211,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.widget.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.widget.html
index 73d5050..ff00794 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.widget.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v17.leanback.widget.html
@@ -239,7 +239,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.app.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.app.html
index b1645a6..ccdb5fe 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.app.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.app.html
@@ -120,7 +120,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.html
index efc62c9..b1b6ffc 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.res.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.res.html
index fd9c9f8..f6a1367 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.res.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.content.res.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.media.session.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.media.session.html
index afd23b1..1182648 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.media.session.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.media.session.html
@@ -120,7 +120,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.view.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.view.html
index 7d74c82..b4fe9b2 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.view.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.view.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.widget.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.widget.html
index 4ca7874..e32ebc81 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.widget.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v4.widget.html
@@ -134,7 +134,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.appcompat.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.appcompat.html
index 553bdf2..b516a35 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.appcompat.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.appcompat.html
@@ -161,7 +161,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.drawable.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.drawable.html
index e9d5cd9..69c91d5 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.drawable.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.drawable.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.html
index ba9a092..13d6ef9 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.graphics.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.media.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.media.html
index 7892bf9..4c2094c 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.media.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.media.html
@@ -134,7 +134,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.preference.html b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.preference.html
index ced3a90..ed590bb 100644
--- a/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.preference.html
+++ b/docs/html/sdk/support_api_diff/23.1.0/changes/pkg_android.support.v7.preference.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_additions.html b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_additions.html
index 09ec1eb..3b43f27 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_additions.html
@@ -1955,7 +1955,7 @@
<!-- Method XYZToLAB -->
<nobr><A HREF="android.support.v4.graphics.ColorUtils.html#android.support.v4.graphics.ColorUtils.XYZToLAB_added(double, double, double, double[])" class="hiddenlink" target="rightframe"><b>XYZToLAB</b>
(<code>double, double, double, double[]</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_all.html b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_all.html
index 770d615..6264757 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_all.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_all.html
@@ -2914,7 +2914,7 @@
<!-- Method XYZToLAB -->
<nobr><A HREF="android.support.v4.graphics.ColorUtils.html#android.support.v4.graphics.ColorUtils.XYZToLAB_added(double, double, double, double[])" class="hiddenlink" target="rightframe"><b>XYZToLAB</b>
(<code>double, double, double, double[]</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_changes.html b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_changes.html
index 134580d..6113a9e 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_changes.html
@@ -939,7 +939,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.WindowCompat.html" class="hiddenlink" target="rightframe">WindowCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_removals.html b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_removals.html
index a60b244..2df39dc 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/alldiffs_index_removals.html
@@ -956,7 +956,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.view.WindowCompat.html#android.support.v4.view.WindowCompat.ctor_removed()" class="hiddenlink" target="rightframe"><strike>WindowCompat</strike>
()</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
index 46a0548..3e97afd 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.html
index 7987724..d7745e6 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsIntent.html
@@ -151,7 +151,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsSession.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsSession.html
index 1296d4c..9b48018 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsSession.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.customtabs.CustomTabsSession.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
index d539481..80c8e11 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
@@ -136,7 +136,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
index e47f317..e1ae295 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
@@ -164,7 +164,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
index 04efe89..ee87ca60 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
@@ -121,7 +121,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.FloatingActionButton.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.FloatingActionButton.html
index 985adba..33d67e2 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.FloatingActionButton.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.FloatingActionButton.html
@@ -144,7 +144,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.HeaderScrollingViewBehavior.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.HeaderScrollingViewBehavior.html
index 7021ba2..0e24356 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.HeaderScrollingViewBehavior.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.HeaderScrollingViewBehavior.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TabLayout.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TabLayout.html
index 9224ddb..3d0eb9f 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TabLayout.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TabLayout.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TextInputLayout.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TextInputLayout.html
index b0f5d63..d72b373 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TextInputLayout.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.TextInputLayout.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.ViewOffsetBehavior.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.ViewOffsetBehavior.html
index 1b4625f..9faa826 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.ViewOffsetBehavior.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.design.widget.ViewOffsetBehavior.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html
index c13de02..13c0b4e 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.percent.PercentLayoutHelper.PercentLayoutInfo.html
@@ -130,7 +130,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v14.preference.PreferenceFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v14.preference.PreferenceFragment.html
index 3a910d0..7ead465 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v14.preference.PreferenceFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v14.preference.PreferenceFragment.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowFragment.html
index 003f04b..9bd2bd7 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowSupportFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowSupportFragment.html
index 6a30cc3..cf9900c 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BaseRowSupportFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseFragment.html
index 74cb151..d7d0e76 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseFragment.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html
index a46a8ed..d7bb8c9 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.BrowseSupportFragment.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsFragment.html
index 34875c3..852b28a6 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
index 4cd41ed..444a537 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.DetailsSupportFragment.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html
index 0962be7..1cfc19e 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepFragment.html
@@ -330,7 +330,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepSupportFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepSupportFragment.html
index b464afa..c5f0d5a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.GuidedStepSupportFragment.html
@@ -330,7 +330,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener.html
index 0df02c4..800f32b 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener.html
index cc2eeda..70ec556 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener.html
index 0c59abd..ee37373 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener.html
index ee27726..99e380d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsFragment.html
index 612c05a..7c78322 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsFragment.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsSupportFragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsSupportFragment.html
index 7cbdf0c..a112b19 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsSupportFragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.app.RowsSupportFragment.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder.html
index 674231e..ef74a40 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.html
index 74e170c..05a2205 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html
index 0a20eb4..f67942e 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidanceStylist.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html
index b77684b..4d1d86b 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.Builder.html
@@ -281,7 +281,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.html
index 20165c7..2ce2773 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedAction.html
@@ -307,7 +307,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html
index 0849383..b9b78cb 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder.html
@@ -195,7 +195,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html
index a8dff6f..fbe6bb4 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.GuidedActionsStylist.html
@@ -306,7 +306,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ImageCardView.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ImageCardView.html
index f110a16..dde52dd 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ImageCardView.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ImageCardView.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ListRowPresenter.ViewHolder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ListRowPresenter.ViewHolder.html
index 2207a6d..9aadb33 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ListRowPresenter.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v17.leanback.widget.ListRowPresenter.ViewHolder.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html
index 2479a86..4545dc6 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.AppOpsManagerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.AppOpsManagerCompat.html
index 15ddb44..8d4b281 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.AppOpsManagerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.AppOpsManagerCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.BundleCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.BundleCompat.html
index 63ecf79e..65cb6ce 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.BundleCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.BundleCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.Fragment.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.Fragment.html
index d6c9e88..adb8e8a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.Fragment.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.Fragment.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentActivity.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentActivity.html
index 72bd4de..843cf68 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentActivity.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentActivity.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentHostCallback.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentHostCallback.html
index fcb311f..ca9cab8 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentHostCallback.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.FragmentHostCallback.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NavUtils.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NavUtils.html
index e4fb7fa..7267e50 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NavUtils.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NavUtils.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NotificationManagerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NotificationManagerCompat.html
index be48aac..06346b2 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NotificationManagerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.NotificationManagerCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.RemoteInput.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.RemoteInput.html
index a6a2a2d..006eeee 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.RemoteInput.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.RemoteInput.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ServiceCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ServiceCompat.html
index c038e52..613affe 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ServiceCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ServiceCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ShareCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ShareCompat.html
index 8e9c945..90c0630 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ShareCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.ShareCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.TaskStackBuilder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.TaskStackBuilder.html
index 8a8a3c7..12d44de 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.TaskStackBuilder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.app.TaskStackBuilder.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContentResolverCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContentResolverCompat.html
index 6abd31a..37ab80d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContentResolverCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContentResolverCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContextCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContextCompat.html
index c0abad2..92f9469 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContextCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ContextCompat.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.IntentCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.IntentCompat.html
index 707b2de..37a8b42 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.IntentCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.IntentCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.LocalBroadcastManager.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.LocalBroadcastManager.html
index fb0f8eb..a3d8f89 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.LocalBroadcastManager.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.LocalBroadcastManager.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ParallelExecutorCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ParallelExecutorCompat.html
index 135c65a..3dd3598 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ParallelExecutorCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.ParallelExecutorCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.EditorCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.EditorCompat.html
index 2a46cc87..5159fec 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.EditorCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.EditorCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.html
index 99e7e84..3c4513a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.SharedPreferencesCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.pm.ActivityInfoCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.pm.ActivityInfoCompat.html
index 201f6e2..1fa3936 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.pm.ActivityInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.pm.ActivityInfoCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.res.ResourcesCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.res.ResourcesCompat.html
index 497d73b..77e6d43 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.res.ResourcesCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.content.res.ResourcesCompat.html
@@ -137,7 +137,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.database.DatabaseUtilsCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.database.DatabaseUtilsCompat.html
index 0feb04b..90a5d72 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.database.DatabaseUtilsCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.database.DatabaseUtilsCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.BitmapCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.BitmapCompat.html
index 3b50f8e..912d5ee 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.BitmapCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.BitmapCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.ColorUtils.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.ColorUtils.html
index 68c2dce..1dfcdc5 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.ColorUtils.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.ColorUtils.html
@@ -186,7 +186,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html
index 735909e..041cbfd 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html
@@ -152,7 +152,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory.html
index f0e4cbf..2323bf0 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.hardware.fingerprint.FingerprintManagerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.hardware.fingerprint.FingerprintManagerCompat.html
index 3c5e74d..8a49a32 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.hardware.fingerprint.FingerprintManagerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.hardware.fingerprint.FingerprintManagerCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.ConnectivityManagerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.ConnectivityManagerCompat.html
index 585fbe1..45e77fa 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.ConnectivityManagerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.ConnectivityManagerCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.TrafficStatsCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.TrafficStatsCompat.html
index d8d8d74..89403e7 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.TrafficStatsCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.net.TrafficStatsCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.AsyncTaskCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.AsyncTaskCompat.html
index 0ea4b47..fcde361 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.AsyncTaskCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.AsyncTaskCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.EnvironmentCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.EnvironmentCompat.html
index 4d625b1..914f3f0 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.EnvironmentCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.EnvironmentCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.ParcelableCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.ParcelableCompat.html
index 0192ed6..8933edd 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.ParcelableCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.ParcelableCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.TraceCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.TraceCompat.html
index 3f490ba..8778c2e4 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.TraceCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.os.TraceCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.ICUCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.ICUCompat.html
index 2b97440..c4ea966 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.ICUCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.ICUCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextDirectionHeuristicsCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextDirectionHeuristicsCompat.html
index 45557e1..351a162 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextDirectionHeuristicsCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextDirectionHeuristicsCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextUtilsCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextUtilsCompat.html
index 16d0835..d3ab7ce 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextUtilsCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.text.TextUtilsCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GestureDetectorCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GestureDetectorCompat.html
index 5c0463d..e66c261 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GestureDetectorCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GestureDetectorCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GravityCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GravityCompat.html
index 9ee14c5..95c665e 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GravityCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.GravityCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.InputDeviceCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.InputDeviceCompat.html
index 4a7f385..38a1cd8 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.InputDeviceCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.InputDeviceCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.KeyEventCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.KeyEventCompat.html
index 49526ab..58692a9 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.KeyEventCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.KeyEventCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.LayoutInflaterCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.LayoutInflaterCompat.html
index 7329ff7..b62e876 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.LayoutInflaterCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.LayoutInflaterCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MarginLayoutParamsCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MarginLayoutParamsCompat.html
index 86a7b57..444980d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MarginLayoutParamsCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MarginLayoutParamsCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuCompat.html
index 22ead5ec..17b8b7d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuItemCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuItemCompat.html
index a321ac3..415dae2 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuItemCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MenuItemCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MotionEventCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MotionEventCompat.html
index cb8895d..c1f81ba 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MotionEventCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.MotionEventCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ScaleGestureDetectorCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ScaleGestureDetectorCompat.html
index 23f8718..8733e6b 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ScaleGestureDetectorCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ScaleGestureDetectorCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.VelocityTrackerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.VelocityTrackerCompat.html
index 31a2b66..078bb96 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.VelocityTrackerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.VelocityTrackerCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewCompat.html
index bbfebc1..0647acb 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html
index 3deb282..0721e0c 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewGroupCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewGroupCompat.html
index 973f25d..7c3f280 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewGroupCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewGroupCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewParentCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewParentCompat.html
index 0ff6977..186cdce 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewParentCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewParentCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html
index dcb75fc..7611f00 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.ViewPropertyAnimatorCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.WindowCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.WindowCompat.html
index a9711fa..bf16942a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.WindowCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.WindowCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
index e2045d7..b9b15c8 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html
index d74f936..5180523 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.animation.PathInterpolatorCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.animation.PathInterpolatorCompat.html
index 6701f77..715f73f 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.animation.PathInterpolatorCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.view.animation.PathInterpolatorCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.DrawerLayout.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.DrawerLayout.html
index c2e7c83..f859501 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.DrawerLayout.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.DrawerLayout.html
@@ -148,7 +148,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.EdgeEffectCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.EdgeEffectCompat.html
index 6993bec..a476dfb 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.EdgeEffectCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.EdgeEffectCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ListPopupWindowCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ListPopupWindowCompat.html
index fc5ed8d..243f6d2 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ListPopupWindowCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ListPopupWindowCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupMenuCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupMenuCompat.html
index 1d55628..c3cca70 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupMenuCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupMenuCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupWindowCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupWindowCompat.html
index 215118c..0523e9f 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupWindowCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.PopupWindowCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ScrollerCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ScrollerCompat.html
index ea76c92..f13086d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ScrollerCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.ScrollerCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.SearchViewCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.SearchViewCompat.html
index ce5abd3..e3de11d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.SearchViewCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.SearchViewCompat.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.TextViewCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.TextViewCompat.html
index e2078f4..e3a0f9a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.TextViewCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v4.widget.TextViewCompat.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.app.AppCompatDelegate.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.app.AppCompatDelegate.html
index 6fd286c..2dbee43 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.app.AppCompatDelegate.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.app.AppCompatDelegate.html
@@ -172,7 +172,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.attr.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.attr.html
index c399a8a..2a2364a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.attr.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.attr.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.drawable.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.drawable.html
index ec1ee38..a2e1dc3 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.drawable.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.drawable.html
@@ -347,7 +347,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.style.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.style.html
index 478d333..32befe4 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.style.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.style.html
@@ -178,7 +178,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.styleable.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.styleable.html
index 498ec51..6433bc2 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.styleable.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.appcompat.R.styleable.html
@@ -1705,7 +1705,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.Builder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.Builder.html
index 83f441b..2758189 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.Builder.html
@@ -140,7 +140,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.html
index be157a7..d7752f5 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.graphics.Palette.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html
index 7db6666..9181648 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.Builder.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.html
index 315ba3d..0cb71cf 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouteDescriptor.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouter.Callback.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouter.Callback.html
index 66be68d..5d68006 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouter.Callback.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.media.MediaRouter.Callback.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceFragmentCompat.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceFragmentCompat.html
index 68aa733..980ff0d 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceFragmentCompat.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceFragmentCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceScreen.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceScreen.html
index 12de78c..de14ee0 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceScreen.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceScreen.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceViewHolder.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceViewHolder.html
index b27e66c..0597c2a 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceViewHolder.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.preference.PreferenceViewHolder.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.recyclerview.R.dimen.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.recyclerview.R.dimen.html
index b38c6dc..a56e112 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.recyclerview.R.dimen.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.recyclerview.R.dimen.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.OrientationHelper.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.OrientationHelper.html
index 6a2a9b3..9111877 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.OrientationHelper.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.OrientationHelper.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.ItemAnimator.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.ItemAnimator.html
index 95e642e..a641975 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.ItemAnimator.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.ItemAnimator.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.LayoutManager.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.LayoutManager.html
index 30b0a78..173d636 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.LayoutManager.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.LayoutManager.html
@@ -193,7 +193,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.State.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.State.html
index 2b3ac61..86d91dc 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.State.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.State.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.html
index 5cdc7a0..a53430b 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.RecyclerView.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.helper.ItemTouchHelper.Callback.html b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.helper.ItemTouchHelper.Callback.html
index 1237408..b241e6e 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.helper.ItemTouchHelper.Callback.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/android.support.v7.widget.helper.ItemTouchHelper.Callback.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/changes-summary.html b/docs/html/sdk/support_api_diff/23.2.0/changes/changes-summary.html
index d41ec1f..431b2ae 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/changes-summary.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/changes-summary.html
@@ -352,7 +352,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23.2.0/changes/classes_index_additions.html b/docs/html/sdk/support_api_diff/23.2.0/changes/classes_index_additions.html
index a2e1d44..eb0457c 100644
--- a/docs/html/sdk/support_api_diff/23.2.0/changes/classes_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23.2.0/changes/classes_index_additions.html
@@ -143,7 +143,7 @@
<p><div style="line-height:1.5em;color:black">
<A HREF="pkg_android.support.v17.leanback.widget.html#ViewHolderTask" class="hiddenlink" target="rightframe"><b><i>ViewHolderTask</i></b></A><br>
<A HREF="pkg_android.support.design.widget.html#VisibilityAwareImageButton" class="hiddenlink" target="rightframe"><b>VisibilityAwareImageButton</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_additions.html b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_additions.html
index 78c94e1..c526b45 100644
--- a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_additions.html
@@ -1128,7 +1128,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Colored" class="hiddenlink" target="rightframe">Widget_AppCompat_Button_Colored</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_all.html b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_all.html
index f39d82e..61459ad 100644
--- a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_all.html
+++ b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_all.html
@@ -1457,7 +1457,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Colored" class="hiddenlink" target="rightframe">Widget_AppCompat_Button_Colored</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_changes.html b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_changes.html
index debbb14..0a63a58 100644
--- a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_changes.html
@@ -485,7 +485,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.ViewCompat.html" class="hiddenlink" target="rightframe">ViewCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_removals.html b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_removals.html
index a6cdb59..9f13a01 100644
--- a/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23/changes/alldiffs_index_removals.html
@@ -332,7 +332,7 @@
<!-- Field View_backgroundTintMode -->
<nobr><A HREF="android.support.v7.appcompat.R.styleable.html#android.support.v7.appcompat.R.styleable.View_backgroundTintMode" class="hiddenlink" target="rightframe"><strike>View_backgroundTintMode</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.Behavior.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.Behavior.html
index a6bbf2d..352dcfd 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.Behavior.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.Behavior.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.html
index 71e3167..cd2a11f 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.AppBarLayout.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.CollapsingToolbarLayout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.CollapsingToolbarLayout.html
index 7244be9..d80f7ee 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.CollapsingToolbarLayout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.CollapsingToolbarLayout.html
@@ -150,7 +150,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.NavigationView.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.NavigationView.html
index a38990a..5dd3104 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.NavigationView.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.NavigationView.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.Snackbar.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.Snackbar.html
index 9a53448..2247297 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.Snackbar.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.Snackbar.html
@@ -116,7 +116,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.Tab.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.Tab.html
index b7a01f5..a0ea052 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.Tab.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.Tab.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.html
index df626ac..bd3c8e6 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TabLayout.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TextInputLayout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TextInputLayout.html
index 471e267..a9a7b49 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TextInputLayout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.design.widget.TextInputLayout.html
@@ -165,7 +165,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v13.app.FragmentCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v13.app.FragmentCompat.html
index 9eb5b24..b61fb5a 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v13.app.FragmentCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v13.app.FragmentCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder.html
index 3c6165f..f189083 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.html
index b6d8863..cb2156a 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.html
@@ -179,7 +179,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.animation.AnimatorCompatHelper.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.animation.AnimatorCompatHelper.html
index 092a6c3..5d5a9fc 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.animation.AnimatorCompatHelper.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.animation.AnimatorCompatHelper.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.ActivityCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.ActivityCompat.html
index 76e8ce9..c318b70 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.ActivityCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.ActivityCompat.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.Fragment.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.Fragment.html
index 53a8bd5..2f56f59 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.Fragment.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.Fragment.html
@@ -190,7 +190,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.FragmentActivity.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.FragmentActivity.html
index 8d2321f..0338069 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.FragmentActivity.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.app.FragmentActivity.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.AsyncTaskLoader.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.AsyncTaskLoader.html
index 19be27c..5dfbe6c 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.AsyncTaskLoader.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.AsyncTaskLoader.html
@@ -133,7 +133,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.ContextCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.ContextCompat.html
index 6ed5b3e..64d09ac 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.ContextCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.ContextCompat.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.Loader.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.Loader.html
index d4c3bb0..f97def1 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.Loader.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.content.Loader.html
@@ -136,7 +136,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.DrawableCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.DrawableCompat.html
index 87fefee..ca42f3d 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.DrawableCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.DrawableCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawable.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawable.html
index 7c72cf4..793bd8b 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawable.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.graphics.drawable.RoundedBitmapDrawable.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.Builder.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.Builder.html
index c429c39..6ae7186 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.Builder.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.Builder.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.html
index 4053435..dde12a5 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.MediaDescriptionCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaControllerCompat.TransportControls.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaControllerCompat.TransportControls.html
index 4d10769..12743cd 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaControllerCompat.TransportControls.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaControllerCompat.TransportControls.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaSessionCompat.Callback.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaSessionCompat.Callback.html
index a0bbbf6..4acf244 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaSessionCompat.Callback.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.MediaSessionCompat.Callback.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.PlaybackStateCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.PlaybackStateCompat.html
index 29e5f0b..0f568c4 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.PlaybackStateCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.media.session.PlaybackStateCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.text.ICUCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.text.ICUCompat.html
index 5f9f31e..e357c50 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.text.ICUCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.text.ICUCompat.html
@@ -130,7 +130,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.ViewCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.ViewCompat.html
index 548502c..caaf19a 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.ViewCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.ViewCompat.html
@@ -122,7 +122,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html
index ce98f64..c60e7e1 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html
@@ -255,7 +255,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html
index f3ef666..6f2e809 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html
@@ -361,7 +361,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.DrawerLayout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.DrawerLayout.html
index 3297157..c18ae6c 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.DrawerLayout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.DrawerLayout.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.ExploreByTouchHelper.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.ExploreByTouchHelper.html
index 4026cd2..be8512f 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.ExploreByTouchHelper.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.ExploreByTouchHelper.html
@@ -123,7 +123,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.PopupWindowCompat.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.PopupWindowCompat.html
index 8a9b9c6..284dab4 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.PopupWindowCompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.PopupWindowCompat.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.SwipeRefreshLayout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.SwipeRefreshLayout.html
index 91a99cb..1fb912f 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.SwipeRefreshLayout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v4.widget.SwipeRefreshLayout.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.app.AppCompatDelegate.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.app.AppCompatDelegate.html
index 42ef289..9f768b9 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.app.AppCompatDelegate.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.app.AppCompatDelegate.html
@@ -137,7 +137,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.attr.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.attr.html
index cc28516..98d2c2c 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.attr.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.attr.html
@@ -221,7 +221,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.color.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.color.html
index 5f1a3f6..852d595 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.color.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.color.html
@@ -193,7 +193,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.dimen.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.dimen.html
index c4fba92..893ea25 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.dimen.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.dimen.html
@@ -158,7 +158,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.drawable.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.drawable.html
index 0bf5ce1..482e9ff 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.drawable.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.drawable.html
@@ -144,7 +144,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.id.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.id.html
index 2b34b3a..818a5b0 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.id.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.id.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.layout.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.layout.html
index 9d24903..a0a33fb 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.layout.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.layout.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.style.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.style.html
index 8b16f1c..d084de0 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.style.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.style.html
@@ -207,7 +207,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.styleable.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.styleable.html
index 5365376..c021cbe 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.styleable.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.appcompat.R.styleable.html
@@ -333,7 +333,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.graphics.Palette.Builder.html b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.graphics.Palette.Builder.html
index 3893441..e707730 100644
--- a/docs/html/sdk/support_api_diff/23/changes/android.support.v7.graphics.Palette.Builder.html
+++ b/docs/html/sdk/support_api_diff/23/changes/android.support.v7.graphics.Palette.Builder.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/changes-summary.html b/docs/html/sdk/support_api_diff/23/changes/changes-summary.html
index 3deee46e..75c1eb3 100644
--- a/docs/html/sdk/support_api_diff/23/changes/changes-summary.html
+++ b/docs/html/sdk/support_api_diff/23/changes/changes-summary.html
@@ -310,7 +310,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/classes_index_additions.html b/docs/html/sdk/support_api_diff/23/changes/classes_index_additions.html
index 094fff5..bc69616 100644
--- a/docs/html/sdk/support_api_diff/23/changes/classes_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23/changes/classes_index_additions.html
@@ -160,7 +160,7 @@
<A HREF="pkg_android.support.v4.content.html#SharedPreferencesCompat" class="hiddenlink" target="rightframe"><b>SharedPreferencesCompat</b></A><br>
<A HREF="pkg_android.support.v4.content.html#SharedPreferencesCompat.EditorCompat" class="hiddenlink" target="rightframe"><b>SharedPreferencesCompat.EditorCompat</b></A><br>
<A HREF="pkg_android.support.design.widget.html#Snackbar.Callback" class="hiddenlink" target="rightframe"><b>Snackbar.Callback</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/classes_index_all.html b/docs/html/sdk/support_api_diff/23/changes/classes_index_all.html
index cb1ed5f..33a23bf 100644
--- a/docs/html/sdk/support_api_diff/23/changes/classes_index_all.html
+++ b/docs/html/sdk/support_api_diff/23/changes/classes_index_all.html
@@ -419,7 +419,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.ViewCompat.html" class="hiddenlink" target="rightframe">ViewCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/classes_index_changes.html b/docs/html/sdk/support_api_diff/23/changes/classes_index_changes.html
index b674f27..9588a73 100644
--- a/docs/html/sdk/support_api_diff/23/changes/classes_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23/changes/classes_index_changes.html
@@ -328,7 +328,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.ViewCompat.html" class="hiddenlink" target="rightframe">ViewCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/classes_index_removals.html b/docs/html/sdk/support_api_diff/23/changes/classes_index_removals.html
index e6da73f..c466298 100644
--- a/docs/html/sdk/support_api_diff/23/changes/classes_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23/changes/classes_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/constructors_index_additions.html b/docs/html/sdk/support_api_diff/23/changes/constructors_index_additions.html
index d1e5215..ae8c0d3 100644
--- a/docs/html/sdk/support_api_diff/23/changes/constructors_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23/changes/constructors_index_additions.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.design.widget.TextInputLayout.html#android.support.design.widget.TextInputLayout.ctor_added(android.content.Context, android.util.AttributeSet, int)" class="hiddenlink" target="rightframe"><b>TextInputLayout</b>
(<code>Context, AttributeSet, int</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/constructors_index_all.html b/docs/html/sdk/support_api_diff/23/changes/constructors_index_all.html
index 4618d88..401df57 100644
--- a/docs/html/sdk/support_api_diff/23/changes/constructors_index_all.html
+++ b/docs/html/sdk/support_api_diff/23/changes/constructors_index_all.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.design.widget.TextInputLayout.html#android.support.design.widget.TextInputLayout.ctor_added(android.content.Context, android.util.AttributeSet, int)" class="hiddenlink" target="rightframe"><b>TextInputLayout</b>
(<code>Context, AttributeSet, int</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/constructors_index_changes.html b/docs/html/sdk/support_api_diff/23/changes/constructors_index_changes.html
index a5ca2ef..122cdce 100644
--- a/docs/html/sdk/support_api_diff/23/changes/constructors_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23/changes/constructors_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/constructors_index_removals.html b/docs/html/sdk/support_api_diff/23/changes/constructors_index_removals.html
index 74a09ba..dadd1cd 100644
--- a/docs/html/sdk/support_api_diff/23/changes/constructors_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23/changes/constructors_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/fields_index_additions.html b/docs/html/sdk/support_api_diff/23/changes/fields_index_additions.html
index 2f6ac9c..f9137b8 100644
--- a/docs/html/sdk/support_api_diff/23/changes/fields_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23/changes/fields_index_additions.html
@@ -444,7 +444,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Colored" class="hiddenlink" target="rightframe">Widget_AppCompat_Button_Colored</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/fields_index_all.html b/docs/html/sdk/support_api_diff/23/changes/fields_index_all.html
index a7f5e18..909dbcc 100644
--- a/docs/html/sdk/support_api_diff/23/changes/fields_index_all.html
+++ b/docs/html/sdk/support_api_diff/23/changes/fields_index_all.html
@@ -538,7 +538,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.appcompat.R.style.html#android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Colored" class="hiddenlink" target="rightframe">Widget_AppCompat_Button_Colored</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/fields_index_changes.html b/docs/html/sdk/support_api_diff/23/changes/fields_index_changes.html
index 0e2ccff..301881a 100644
--- a/docs/html/sdk/support_api_diff/23/changes/fields_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23/changes/fields_index_changes.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/fields_index_removals.html b/docs/html/sdk/support_api_diff/23/changes/fields_index_removals.html
index 5027374..174a576 100644
--- a/docs/html/sdk/support_api_diff/23/changes/fields_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23/changes/fields_index_removals.html
@@ -241,7 +241,7 @@
</nobr><br>
<nobr><A HREF="android.support.v7.appcompat.R.styleable.html#android.support.v7.appcompat.R.styleable.View_backgroundTintMode" class="hiddenlink" target="rightframe"><strike>View_backgroundTintMode</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/jdiff_help.html b/docs/html/sdk/support_api_diff/23/changes/jdiff_help.html
index acb8508..125d4e8 100644
--- a/docs/html/sdk/support_api_diff/23/changes/jdiff_help.html
+++ b/docs/html/sdk/support_api_diff/23/changes/jdiff_help.html
@@ -120,7 +120,7 @@
There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass.
In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes.
</BLOCKQUOTE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/jdiff_statistics.html b/docs/html/sdk/support_api_diff/23/changes/jdiff_statistics.html
index 4480788..1e865b7 100644
--- a/docs/html/sdk/support_api_diff/23/changes/jdiff_statistics.html
+++ b/docs/html/sdk/support_api_diff/23/changes/jdiff_statistics.html
@@ -467,7 +467,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/jdiff_topleftframe.html b/docs/html/sdk/support_api_diff/23/changes/jdiff_topleftframe.html
index 36f9836..6a2e76e 100644
--- a/docs/html/sdk/support_api_diff/23/changes/jdiff_topleftframe.html
+++ b/docs/html/sdk/support_api_diff/23/changes/jdiff_topleftframe.html
@@ -49,7 +49,7 @@
<TD NOWRAP><FONT CLASS="indexText" size="-2"><A HREF="fields_index_all.html" TARGET="bottomleftframe">By Field</A></FONT><br></TD>
</TR>
</TABLE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/methods_index_additions.html b/docs/html/sdk/support_api_diff/23/changes/methods_index_additions.html
index 3cec9a3..c3e4176 100644
--- a/docs/html/sdk/support_api_diff/23/changes/methods_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23/changes/methods_index_additions.html
@@ -529,7 +529,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.app.FragmentActivity.html#android.support.v4.app.FragmentActivity.validateRequestPermissionsRequestCode_added(int)" class="hiddenlink" target="rightframe"><b>validateRequestPermissionsRequestCode</b>
(<code>int</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/methods_index_all.html b/docs/html/sdk/support_api_diff/23/changes/methods_index_all.html
index 072b2ff..7b2f1d4 100644
--- a/docs/html/sdk/support_api_diff/23/changes/methods_index_all.html
+++ b/docs/html/sdk/support_api_diff/23/changes/methods_index_all.html
@@ -550,7 +550,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.app.FragmentActivity.html#android.support.v4.app.FragmentActivity.validateRequestPermissionsRequestCode_added(int)" class="hiddenlink" target="rightframe"><b>validateRequestPermissionsRequestCode</b>
(<code>int</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/methods_index_changes.html b/docs/html/sdk/support_api_diff/23/changes/methods_index_changes.html
index d190580..90a0a23 100644
--- a/docs/html/sdk/support_api_diff/23/changes/methods_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23/changes/methods_index_changes.html
@@ -83,7 +83,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.html#android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.setBackgroundColor_changed(int)" class="hiddenlink" target="rightframe">setBackgroundColor
(<code>int</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/methods_index_removals.html b/docs/html/sdk/support_api_diff/23/changes/methods_index_removals.html
index 25175750..ce89d27 100644
--- a/docs/html/sdk/support_api_diff/23/changes/methods_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23/changes/methods_index_removals.html
@@ -71,7 +71,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.app.Fragment.html#android.support.v4.app.Fragment.onAttach_removed(android.app.Activity)" class="hiddenlink" target="rightframe"><strike>onAttach</strike>
(<code>Activity</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/packages_index_additions.html b/docs/html/sdk/support_api_diff/23/changes/packages_index_additions.html
index 446ee58..fc6d24d 100644
--- a/docs/html/sdk/support_api_diff/23/changes/packages_index_additions.html
+++ b/docs/html/sdk/support_api_diff/23/changes/packages_index_additions.html
@@ -59,7 +59,7 @@
<A HREF="changes-summary.html#android.support.v7.graphics.drawable" class="hiddenlink" target="rightframe"><b>android.support.v7.graphics.drawable</b></A><br>
<A HREF="changes-summary.html#android.support.v7.preference" class="hiddenlink" target="rightframe"><b>android.support.v7.preference</b></A><br>
<A HREF="changes-summary.html#android.support.v8.renderscript" class="hiddenlink" target="rightframe"><b>android.support.v8.renderscript</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/packages_index_all.html b/docs/html/sdk/support_api_diff/23/changes/packages_index_all.html
index d1dbbe1..c770167 100644
--- a/docs/html/sdk/support_api_diff/23/changes/packages_index_all.html
+++ b/docs/html/sdk/support_api_diff/23/changes/packages_index_all.html
@@ -77,7 +77,7 @@
<A HREF="changes-summary.html#android.support.v7.preference" class="hiddenlink" target="rightframe"><b>android.support.v7.preference</b></A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
<A HREF="changes-summary.html#android.support.v8.renderscript" class="hiddenlink" target="rightframe"><b>android.support.v8.renderscript</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/packages_index_changes.html b/docs/html/sdk/support_api_diff/23/changes/packages_index_changes.html
index 730633e..2140313 100644
--- a/docs/html/sdk/support_api_diff/23/changes/packages_index_changes.html
+++ b/docs/html/sdk/support_api_diff/23/changes/packages_index_changes.html
@@ -68,7 +68,7 @@
<A HREF="pkg_android.support.v7.appcompat.html" class="hiddenlink" target="rightframe">android.support.v7.appcompat</A><br>
<A HREF="pkg_android.support.v7.graphics.html" class="hiddenlink" target="rightframe">android.support.v7.graphics</A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/packages_index_removals.html b/docs/html/sdk/support_api_diff/23/changes/packages_index_removals.html
index d0ffabc..d5a825d 100644
--- a/docs/html/sdk/support_api_diff/23/changes/packages_index_removals.html
+++ b/docs/html/sdk/support_api_diff/23/changes/packages_index_removals.html
@@ -49,7 +49,7 @@
</div>
<br>
<div id="indexTableEntries">
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.design.widget.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.design.widget.html
index a8630f7..6ebc3a7 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.design.widget.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.design.widget.html
@@ -169,7 +169,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v13.app.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v13.app.html
index c56e8a4..877fd6c 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v13.app.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v13.app.html
@@ -120,7 +120,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v17.leanback.widget.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v17.leanback.widget.html
index 3d7f780..ea3848e 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v17.leanback.widget.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v17.leanback.widget.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.animation.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.animation.html
index 36965dd..4a51cf8 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.animation.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.animation.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.app.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.app.html
index 692e7ef..9fbee40 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.app.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.app.html
@@ -176,7 +176,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.content.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.content.html
index 44e9f81..9e654a6 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.content.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.content.html
@@ -169,7 +169,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.graphics.drawable.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.graphics.drawable.html
index 6139c8e..de77561 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.graphics.drawable.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.graphics.drawable.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.html
index e6fe510..761e746 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.session.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.session.html
index 9b0e472..18893f4 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.session.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.media.session.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.os.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.os.html
index 9a76a7b..2ed8d04 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.os.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.os.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.text.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.text.html
index b2a178a..c995ed8 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.text.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.text.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.accessibility.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.accessibility.html
index dcdbc56..ec69556 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.accessibility.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.accessibility.html
@@ -127,7 +127,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.html
index 2a955d3..2361c0f 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.view.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.widget.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.widget.html
index 482bf1f..c6b5e0c 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.widget.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v4.widget.html
@@ -141,7 +141,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.app.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.app.html
index 1dd5425..a99b641 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.app.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.app.html
@@ -120,7 +120,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.appcompat.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.appcompat.html
index e014278..eb5ed08 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.appcompat.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.appcompat.html
@@ -154,7 +154,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.graphics.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.graphics.html
index cb6c97b..9f88138 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.graphics.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.graphics.html
@@ -120,7 +120,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.util.html b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.util.html
index cc0065d..e4da229 100644
--- a/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.util.html
+++ b/docs/html/sdk/support_api_diff/23/changes/pkg_android.support.v7.util.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_additions.html b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_additions.html
index cb62aa8..f73d864 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_additions.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_additions.html
@@ -933,7 +933,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.view.WindowInsetsCompat.html#android.support.v4.view.WindowInsetsCompat.ctor_added(android.support.v4.view.WindowInsetsCompat)" class="hiddenlink" target="rightframe"><b>WindowInsetsCompat</b>
(<code>WindowInsetsCompat</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_all.html b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_all.html
index 719f9af..fb553f8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_all.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_all.html
@@ -1313,7 +1313,7 @@
<!-- Constructor WindowInsetsCompat -->
<nobr><A HREF="android.support.v4.view.WindowInsetsCompat.html#android.support.v4.view.WindowInsetsCompat.ctor_added(android.support.v4.view.WindowInsetsCompat)" class="hiddenlink" target="rightframe"><b>WindowInsetsCompat</b>
(<code>WindowInsetsCompat</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_changes.html b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_changes.html
index 854a487..4718b46 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_changes.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_changes.html
@@ -652,7 +652,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.WindowInsetsCompat.html" class="hiddenlink" target="rightframe">WindowInsetsCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_removals.html b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_removals.html
index e6d9e07..179472a32 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_removals.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/alldiffs_index_removals.html
@@ -110,7 +110,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.widget.StaggeredGridLayoutManager.html#android.support.v7.widget.StaggeredGridLayoutManager.TAG" class="hiddenlink" target="rightframe"><strike>TAG</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
index d6a9415..41ac368 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.Builder.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.html
index dd0b511..c4f0bfb 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsIntent.html
@@ -130,7 +130,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsSession.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsSession.html
index 16cac31..1ccaab3 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsSession.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.customtabs.CustomTabsSession.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
index 7e1e59a..ad3dfd8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.AppBarLayout.ScrollingViewBehavior.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.BottomSheetBehavior.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.BottomSheetBehavior.html
index e98c6f3..22984b8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.BottomSheetBehavior.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.BottomSheetBehavior.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
index ebfdce1..a20a1ed 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CollapsingToolbarLayout.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
index 8cf6146..edc96ed 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.Behavior.html
@@ -147,7 +147,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.LayoutParams.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.LayoutParams.html
index a36eab0..445da88 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.LayoutParams.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.LayoutParams.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.html
index 4a7247a..3ba5c1e 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.CoordinatorLayout.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html
index 633db38..07099d6 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.FloatingActionButton.Behavior.html
@@ -144,7 +144,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TabLayout.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TabLayout.html
index 01d4b6d..a09656a 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TabLayout.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TabLayout.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TextInputLayout.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TextInputLayout.html
index ecd6fd4..9ad13eb 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TextInputLayout.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.design.widget.TextInputLayout.html
@@ -171,7 +171,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v14.preference.PreferenceFragment.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v14.preference.PreferenceFragment.html
index 9b31c9e..6b23c17 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v14.preference.PreferenceFragment.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v14.preference.PreferenceFragment.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder.html
index 9df6536..9f1f00c 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder.html
@@ -136,7 +136,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.html
index 16b03ad..a0b0aa9 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.AbstractMediaItemPresenter.html
@@ -151,7 +151,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.ObjectAdapter.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.ObjectAdapter.html
index 3e3ded0..3dad1a2 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.ObjectAdapter.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.leanback.widget.ObjectAdapter.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.preference.LeanbackSettingsFragment.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.preference.LeanbackSettingsFragment.html
index 6e544651..690328a 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.preference.LeanbackSettingsFragment.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v17.preference.LeanbackSettingsFragment.html
@@ -94,7 +94,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html
index 0600e44..a353745 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityCompat.html
index 74ae6f6..95b4fc6 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityCompat.html
@@ -129,7 +129,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityOptionsCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityOptionsCompat.html
index ce96b49..e675b7f 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityOptionsCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ActivityOptionsCompat.html
@@ -165,7 +165,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.FragmentController.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.FragmentController.html
index 13b2ce1..dfc1a60 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.FragmentController.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.FragmentController.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ServiceCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ServiceCompat.html
index 6a404eb..211a88f 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ServiceCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.ServiceCompat.html
@@ -130,7 +130,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.SharedElementCallback.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.SharedElementCallback.html
index 1495fb4..7394059 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.SharedElementCallback.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.app.SharedElementCallback.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.content.ContextCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.content.ContextCompat.html
index 1d6b9b1..b6157a2 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.content.ContextCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.content.ContextCompat.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html
index 3408caa..92f1ba4 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.graphics.drawable.DrawableCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserCompat.MediaItem.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserCompat.MediaItem.html
index acc16c1..52da3d4 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserCompat.MediaItem.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserCompat.MediaItem.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserServiceCompat.BrowserRoot.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserServiceCompat.BrowserRoot.html
index 7a9c73311..7ea97f3 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserServiceCompat.BrowserRoot.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaBrowserServiceCompat.BrowserRoot.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaDescriptionCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaDescriptionCompat.html
index fbbaa19..fa1d610 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaDescriptionCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaDescriptionCompat.html
@@ -157,7 +157,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaMetadataCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaMetadataCompat.html
index e52493b..7e0f137 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaMetadataCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.MediaMetadataCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaButtonReceiver.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaButtonReceiver.html
index 3e2c68b..65467b8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaButtonReceiver.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaButtonReceiver.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.QueueItem.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.QueueItem.html
index 5ce173f..e3b0184 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.QueueItem.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.QueueItem.html
@@ -133,7 +133,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.html
index ee71244..90bd5eb 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.MediaSessionCompat.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html
index 2c54c1d..07e2449 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.media.session.PlaybackStateCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.os.BuildCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.os.BuildCompat.html
index eb59ddb..372415a 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.os.BuildCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.os.BuildCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.KeyEventCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.KeyEventCompat.html
index b5f4371..d62437e 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.KeyEventCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.KeyEventCompat.html
@@ -141,7 +141,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.MotionEventCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.MotionEventCompat.html
index 9f85dab..8f58037 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.MotionEventCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.MotionEventCompat.html
@@ -161,7 +161,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewCompat.html
index 1b4ea0f..0974230 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewCompat.html
@@ -181,7 +181,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html
index dcb2023..3eec7fa0a 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.ViewConfigurationCompat.html
@@ -111,7 +111,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.WindowInsetsCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.WindowInsetsCompat.html
index e0f77c9..c5c1d22 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.WindowInsetsCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.WindowInsetsCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
index 6ff46b8..1f9801f 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityEventCompat.html
@@ -158,7 +158,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat.html
index 5d63b93a..923ec80 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat.html
@@ -110,7 +110,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html
index 004d89e..defa406 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityManagerCompat.html
@@ -143,7 +143,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html
index 695028b..d6f719e 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.html
@@ -157,7 +157,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat.html
index 17109ec..d087e4a 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.html
index 30df540..84fc73b 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat.html
index 1d5a250..41abf7e 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html
index 727576d..005616a 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.html
@@ -144,7 +144,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeProviderCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeProviderCompat.html
index 101070c..bd6b7f8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeProviderCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityNodeProviderCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityWindowInfoCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityWindowInfoCompat.html
index a3b8d96..40f3ca2 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityWindowInfoCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.view.accessibility.AccessibilityWindowInfoCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnCloseListenerCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnCloseListenerCompat.html
index cba004e..3d9f2ea 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnCloseListenerCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnCloseListenerCompat.html
@@ -95,7 +95,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat.html
index c15d1b9..df0f3e1 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat.html
@@ -95,7 +95,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.html
index cbb3925..4af1e46 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SearchViewCompat.html
@@ -121,7 +121,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SwipeRefreshLayout.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SwipeRefreshLayout.html
index 77dbdf6..5848782 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SwipeRefreshLayout.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.SwipeRefreshLayout.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.TextViewCompat.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.TextViewCompat.html
index a803634..87b9e69 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.TextViewCompat.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v4.widget.TextViewCompat.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.ActionBarDrawerToggle.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.ActionBarDrawerToggle.html
index 2a864f0..bc8abd6 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.ActionBarDrawerToggle.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.ActionBarDrawerToggle.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.AppCompatDelegate.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.AppCompatDelegate.html
index adfcabb..7b131e6 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.AppCompatDelegate.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.app.AppCompatDelegate.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.content.res.AppCompatResources.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.content.res.AppCompatResources.html
index e26c32b..02f43fd 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.content.res.AppCompatResources.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.content.res.AppCompatResources.html
@@ -108,7 +108,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.graphics.Palette.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.graphics.Palette.html
index f588384..1999a48 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.graphics.Palette.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.graphics.Palette.html
@@ -115,7 +115,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.util.SortedList.Callback.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.util.SortedList.Callback.html
index d2350979..95be79c 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.util.SortedList.Callback.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.util.SortedList.Callback.html
@@ -138,7 +138,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearLayoutManager.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearLayoutManager.html
index cf5c0f6..2fdfb0e 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearLayoutManager.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearLayoutManager.html
@@ -109,7 +109,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearSmoothScroller.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearSmoothScroller.html
index 3e13735..5cf79ee 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearSmoothScroller.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.LinearSmoothScroller.html
@@ -112,7 +112,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.RecyclerView.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.RecyclerView.html
index d5e36f0..5411a1f 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.RecyclerView.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.RecyclerView.html
@@ -116,7 +116,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.StaggeredGridLayoutManager.html b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.StaggeredGridLayoutManager.html
index 640ef38..9d0ab87 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.StaggeredGridLayoutManager.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/android.support.v7.widget.StaggeredGridLayoutManager.html
@@ -124,7 +124,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/changes-summary.html b/docs/html/sdk/support_api_diff/24.2.0/changes/changes-summary.html
index 12dfb4e..4456c22 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/changes-summary.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/changes-summary.html
@@ -315,7 +315,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_additions.html b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_additions.html
index f3eb2ed..acb8115 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_additions.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_additions.html
@@ -291,7 +291,7 @@
<A HREF="pkg_android.support.v7.widget.html#Toolbar.LayoutParams" class="hiddenlink" target="rightframe"><b>Toolbar.LayoutParams</b></A><br>
<A HREF="pkg_android.support.v7.widget.html#Toolbar.OnMenuItemClickListener" class="hiddenlink" target="rightframe"><b><i>Toolbar.OnMenuItemClickListener</i></b></A><br>
<A HREF="pkg_android.support.v7.widget.html#Toolbar.SavedState" class="hiddenlink" target="rightframe"><b>Toolbar.SavedState</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_all.html b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_all.html
index 410ac8a..3e77bf2 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_all.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_all.html
@@ -514,7 +514,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.WindowInsetsCompat.html" class="hiddenlink" target="rightframe">WindowInsetsCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_changes.html b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_changes.html
index 1baef5c..58fe615 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_changes.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_changes.html
@@ -380,7 +380,7 @@
<a href="#topheader"><font size="-2">TOP</font></a>
<p><div style="line-height:1.5em;color:black">
<A HREF="android.support.v4.view.WindowInsetsCompat.html" class="hiddenlink" target="rightframe">WindowInsetsCompat</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_removals.html b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_removals.html
index e6da73f..c466298 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_removals.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/classes_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_additions.html b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_additions.html
index b0c0587..312effc 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_additions.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_additions.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.view.WindowInsetsCompat.html#android.support.v4.view.WindowInsetsCompat.ctor_added(android.support.v4.view.WindowInsetsCompat)" class="hiddenlink" target="rightframe"><b>WindowInsetsCompat</b>
(<code>WindowInsetsCompat</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_all.html b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_all.html
index 91568db..24310b7 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_all.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_all.html
@@ -71,7 +71,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.view.WindowInsetsCompat.html#android.support.v4.view.WindowInsetsCompat.ctor_added(android.support.v4.view.WindowInsetsCompat)" class="hiddenlink" target="rightframe"><b>WindowInsetsCompat</b>
(<code>WindowInsetsCompat</code>)</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_changes.html b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_changes.html
index b4142ed..62a75f8c 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_changes.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_changes.html
@@ -61,7 +61,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.content.ContextCompat.html#android.support.v4.content.ContextCompat.ctor_changed()" class="hiddenlink" target="rightframe">ContextCompat
()</A></nobr> constructor<br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_removals.html b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_removals.html
index f1a9952..2fbd2e1 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_removals.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/constructors_index_removals.html
@@ -47,7 +47,7 @@
<div id="indexTableCaption" style="background-color:#eee;padding:0 4px 0 4px;font-size:11px;margin-bottom:1em;">
Listed as: <span style="color:#069"><strong>Added</strong></span>, <span style="color:#069"><strike>Removed</strike></span>, <span style="color:#069">Changed</span></font>
</div>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_additions.html b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_additions.html
index 8923dbe..cdb8ee8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_additions.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_additions.html
@@ -253,7 +253,7 @@
</nobr><br>
<nobr><A HREF="android.support.v4.view.accessibility.AccessibilityEventCompat.html#android.support.v4.view.accessibility.AccessibilityEventCompat.TYPE_WINDOWS_CHANGED" class="hiddenlink" target="rightframe">TYPE_WINDOWS_CHANGED</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_all.html b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_all.html
index 19cebb7..14b3e97 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_all.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_all.html
@@ -285,7 +285,7 @@
</nobr><br>
<nobr><A HREF="android.support.v4.view.accessibility.AccessibilityEventCompat.html#android.support.v4.view.accessibility.AccessibilityEventCompat.TYPE_WINDOWS_CHANGED" class="hiddenlink" target="rightframe">TYPE_WINDOWS_CHANGED</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_changes.html b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_changes.html
index b5ab769..c72b2de 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_changes.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_changes.html
@@ -57,7 +57,7 @@
</nobr><br>
<nobr><A HREF="android.support.v4.view.ViewCompat.html#android.support.v4.view.ViewCompat.OVER_SCROLL_NEVER" class="hiddenlink" target="rightframe">OVER_SCROLL_NEVER</A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_removals.html b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_removals.html
index 09ffac4..61820d1 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_removals.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/fields_index_removals.html
@@ -53,7 +53,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.widget.StaggeredGridLayoutManager.html#android.support.v7.widget.StaggeredGridLayoutManager.TAG" class="hiddenlink" target="rightframe"><strike>TAG</strike></A>
</nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_help.html b/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_help.html
index ca0931f..966dda8 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_help.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_help.html
@@ -120,7 +120,7 @@
There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass.
In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes.
</BLOCKQUOTE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_statistics.html b/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_statistics.html
index 698dbec..7010da6 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_statistics.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_statistics.html
@@ -583,7 +583,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_topleftframe.html b/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_topleftframe.html
index 36f9836..6a2e76e 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_topleftframe.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/jdiff_topleftframe.html
@@ -49,7 +49,7 @@
<TD NOWRAP><FONT CLASS="indexText" size="-2"><A HREF="fields_index_all.html" TARGET="bottomleftframe">By Field</A></FONT><br></TD>
</TR>
</TABLE>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_additions.html b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_additions.html
index 508018e..419a607 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_additions.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_additions.html
@@ -434,7 +434,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.media.session.PlaybackStateCompat.html#android.support.v4.media.session.PlaybackStateCompat.toKeyCode_added(long)" class="hiddenlink" target="rightframe"><b>toKeyCode</b>
(<code>long</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_all.html b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_all.html
index a52d5c9..d33bfc0 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_all.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_all.html
@@ -565,7 +565,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v4.media.session.PlaybackStateCompat.html#android.support.v4.media.session.PlaybackStateCompat.toKeyCode_added(long)" class="hiddenlink" target="rightframe"><b>toKeyCode</b>
(<code>long</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_changes.html b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_changes.html
index 5faae42..ef008cb 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_changes.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_changes.html
@@ -208,7 +208,7 @@
(<code>int, Bitmap, String</code>)</A></nobr><br>
<nobr><A HREF="android.support.v4.view.KeyEventCompat.html#android.support.v4.view.KeyEventCompat.startTracking_changed(android.view.KeyEvent)" class="hiddenlink" target="rightframe">startTracking
(<code>KeyEvent</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_removals.html b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_removals.html
index 062ac8f..1c0c6be 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_removals.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/methods_index_removals.html
@@ -79,7 +79,7 @@
<p><div style="line-height:1.5em;color:black">
<nobr><A HREF="android.support.v7.widget.LinearLayoutManager.html#android.support.v7.widget.LinearLayoutManager.prepareForDrop_removed(android.view.View, android.view.View, int, int)" class="hiddenlink" target="rightframe"><strike>prepareForDrop</strike>
(<code>View, View, int, int</code>)</A></nobr><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_additions.html b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_additions.html
index 2eff0f7..bb88d9f 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_additions.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_additions.html
@@ -52,7 +52,7 @@
<A NAME="A"></A>
<A HREF="changes-summary.html#android.support.transition" class="hiddenlink" target="rightframe"><b>android.support.transition</b></A><br>
<A HREF="changes-summary.html#android.support.v4.text.util" class="hiddenlink" target="rightframe"><b>android.support.v4.text.util</b></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_all.html b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_all.html
index 58e51ed..78805db 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_all.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_all.html
@@ -77,7 +77,7 @@
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
<A HREF="pkg_android.support.v7.widget.html" class="hiddenlink" target="rightframe">android.support.v7.widget</A><br>
<A HREF="changes-summary.html#android.support.v8.renderscript" class="hiddenlink" target="rightframe"><strike>android.support.v8.renderscript</strike></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_changes.html b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_changes.html
index 1d9e428..f5f6425 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_changes.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_changes.html
@@ -72,7 +72,7 @@
<A HREF="pkg_android.support.v7.preference.html" class="hiddenlink" target="rightframe">android.support.v7.preference</A><br>
<A HREF="pkg_android.support.v7.util.html" class="hiddenlink" target="rightframe">android.support.v7.util</A><br>
<A HREF="pkg_android.support.v7.widget.html" class="hiddenlink" target="rightframe">android.support.v7.widget</A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_removals.html b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_removals.html
index d52d40d..ce5ca58 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_removals.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/packages_index_removals.html
@@ -53,7 +53,7 @@
<A HREF="changes-summary.html#android.support.v7.appcompat" class="hiddenlink" target="rightframe"><strike>android.support.v7.appcompat</strike></A><br>
<A HREF="changes-summary.html#android.support.v7.recyclerview" class="hiddenlink" target="rightframe"><strike>android.support.v7.recyclerview</strike></A><br>
<A HREF="changes-summary.html#android.support.v8.renderscript" class="hiddenlink" target="rightframe"><strike>android.support.v8.renderscript</strike></A><br>
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.customtabs.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.customtabs.html
index 5959d63..45a31ff 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.customtabs.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.customtabs.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.design.widget.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.design.widget.html
index 510b9bd..a311b48 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.design.widget.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.design.widget.html
@@ -161,7 +161,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v14.preference.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v14.preference.html
index 84efadc6..f24be00 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v14.preference.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v14.preference.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.leanback.widget.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.leanback.widget.html
index 4ce1f32..c07a487 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.leanback.widget.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.leanback.widget.html
@@ -119,7 +119,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.preference.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.preference.html
index ad0aadd..c61d79d 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.preference.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v17.preference.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.accessibilityservice.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.accessibilityservice.html
index e904a76..2c0f684 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.accessibilityservice.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.accessibilityservice.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.app.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.app.html
index f8e4cb1..58ed06f9 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.app.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.app.html
@@ -148,7 +148,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.content.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.content.html
index 31aa1da..65dd616 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.content.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.content.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.graphics.drawable.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.graphics.drawable.html
index b1efab3..f8b31a5 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.graphics.drawable.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.graphics.drawable.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.html
index 612a1a0..5300c86 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.session.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.session.html
index 3e46335..459d0c3 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.session.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.media.session.html
@@ -126,7 +126,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.os.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.os.html
index b250855..6d7f399 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.os.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.os.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.util.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.util.html
index d34506e..6f0e777 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.util.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.util.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.accessibility.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.accessibility.html
index a6362fb..d5d126c 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.accessibility.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.accessibility.html
@@ -190,7 +190,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.html
index 10b1334..d720d41 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.view.html
@@ -133,7 +133,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.widget.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.widget.html
index 5ed0f65..2efef45 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.widget.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v4.widget.html
@@ -162,7 +162,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.app.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.app.html
index 8eb58aa..f36cba5 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.app.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.app.html
@@ -162,7 +162,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.content.res.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.content.res.html
index 90787f7..2e4b703 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.content.res.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.content.res.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.graphics.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.graphics.html
index bd9cf24..439c8d3 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.graphics.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.graphics.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.preference.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.preference.html
index b80e005..b2b781b 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.preference.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.preference.html
@@ -105,7 +105,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.util.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.util.html
index 365022f..c35fb8c 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.util.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.util.html
@@ -148,7 +148,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.widget.html b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.widget.html
index e07d5b8..72e5497 100644
--- a/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.widget.html
+++ b/docs/html/sdk/support_api_diff/24.2.0/changes/pkg_android.support.v7.widget.html
@@ -449,7 +449,7 @@
</div> <!-- end footer -->
</div><!-- end doc-content -->
</div> <!-- end body-content -->
-<script src="http://www.google-analytics.com/ga.js" type="text/javascript">
+<script src="https://www.google-analytics.com/ga.js" type="text/javascript">
</script>
<script type="text/javascript">
try {
diff --git a/docs/html/topic/performance/_book.yaml b/docs/html/topic/performance/_book.yaml
index e053a2c..4021e85 100644
--- a/docs/html/topic/performance/_book.yaml
+++ b/docs/html/topic/performance/_book.yaml
@@ -1,34 +1,61 @@
toc:
-- title: Reducing Network Battery Drain
- path: /topic/performance/power/network/index.html
+- title: Optimizing for Battery Life
+ path: /topic/performance/power/index.html
path_attributes:
- name: description
- value: Access the network while going easy on battery life.
+ value: Learn to make your app more battery-friendly.
section:
- - title: Collecting Network Traffic Data
- path: /topic/performance/power/network/gather-data.html
- - title: Analyzing Network Traffic Data
- path: /topic/performance/power/network/analyze-data.html
- - title: Optimizing User-Initiated Network Use
- path: /topic/performance/power/network/action-user-traffic.html
- - title: Optimizing Server-Initiated Network Use
- path: /topic/performance/power/network/action-server-traffic.html
- - title: Optimizing General Network Use
- path: /topic/performance/power/network/action-any-traffic.html
-- title: Implementing Doze
- path: /training/monitoring-device-state/doze-standby.html
+ - title: Network Use and Battery Consumption
+ path: /topic/performance/power/network/index.html
+ section:
+ - title: Collecting Network Traffic Data
+ path: /topic/performance/power/network/gather-data.html
+ - title: Analyzing Data Traffic
+ path: /topic/performance/power/network/analyze-data.html
+ - title: Optimizing User-Initiated Network Use
+ path: /topic/performance/power/network/action-user-traffic.html
+ - title: Optimizing App-Initiated Network Use
+ path: topic/performance/power/network/action-app-traffic.html
+ - title: Optimizing Server-Initiated Network Use
+ path: /topic/performance/power/network/action-server-traffic.html
+ - title: Optimizing General Network Use
+ path: /topic/performance/power/network/action-any-traffic.html
+ - title: Doze and App Standby
+ path: /training/monitoring-device-state/doze-standby.html
+ path_attributes:
+ - name: description
+ value: Help ensure the device isn't depleting the battery when not in use.
+ - title: Battery Historian
+ path: /topic/performance/power/battery-historian.html
+- title: Rendering
+ path: /topic/performance/rendering/index.html
path_attributes:
- name: description
- value: Help ensure the device isn't depleting the battery when not in use.
+ value: Speed up your app's rendering
+ section:
+ - title: Reducing Overdraw
+ path: /topic/performance/rendering/overdraw.html
+ - title: Performance and View Hierarchies
+ path: /topic/performance/rendering/optimizing-view-hierarchies.html
+ - title: Analyzing with Profile GPU Rendering
+ path: /topic/performance/rendering/profile-gpu.html
+- title: Intelligent Job-Scheduling
+ path: /topic/performance/scheduling.html
+- title: Background Optimization
+ path: /topic/performance/background-optimization.html
+- title: Reducing APK Size
+ path: /topic/performance/reduce-apk-size.html
+- title: Reducing Image Download Sizes
+ path: /topic/performance/network-xfer.html
- title: Launch-Time Performance
path: /topic/performance/launch-time.html
- title: Better Performance through Threading
path: /topic/performance/threads.html
-- title: Optimizing View Hierarchies
- path: /topic/performance/optimizing-view-hierarchies.html
-- title: Background Optimization
- path: /topic/performance/background-optimization.html
-- title: Intelligent Job-Scheduling
- path: /topic/performance/scheduling.html
-- title: Reducing APK Size
- path: /topic/performance/reduce-apk-size.html
+- title: Manage Your App's Memory
+ path: /topic/performance/memory.html
+- title: Overview of Memory Managemement
+ path: /topic/performance/memory-overview.html
+ path_attributes:
+ - name: description
+ value: How to keep your app's memory footprint small in order to improve performance on a variety of mobile devices.
+
diff --git a/docs/html/topic/performance/images/app-rankings.png b/docs/html/topic/performance/images/app-rankings.png
new file mode 100644
index 0000000..9dd60e5
--- /dev/null
+++ b/docs/html/topic/performance/images/app-rankings.png
Binary files differ
diff --git a/docs/html/topic/performance/images/bars.png b/docs/html/topic/performance/images/bars.png
new file mode 100644
index 0000000..3afea46
--- /dev/null
+++ b/docs/html/topic/performance/images/bars.png
Binary files differ
diff --git a/docs/html/topic/performance/images/beforeafterindexed.png b/docs/html/topic/performance/images/beforeafterindexed.png
new file mode 100644
index 0000000..dc7762e
--- /dev/null
+++ b/docs/html/topic/performance/images/beforeafterindexed.png
Binary files differ
diff --git a/docs/html/topic/performance/images/comparison.png b/docs/html/topic/performance/images/comparison.png
new file mode 100644
index 0000000..18f204c
--- /dev/null
+++ b/docs/html/topic/performance/images/comparison.png
Binary files differ
diff --git a/docs/html/topic/performance/images/decisions.png b/docs/html/topic/performance/images/decisions.png
new file mode 100644
index 0000000..d4f21f8
--- /dev/null
+++ b/docs/html/topic/performance/images/decisions.png
Binary files differ
diff --git a/docs/html/topic/performance/images/dropdown.png b/docs/html/topic/performance/images/dropdown.png
new file mode 100644
index 0000000..59ec6110
--- /dev/null
+++ b/docs/html/topic/performance/images/dropdown.png
Binary files differ
diff --git a/docs/html/topic/performance/images/generic-timeline.png b/docs/html/topic/performance/images/generic-timeline.png
new file mode 100644
index 0000000..04388b6
--- /dev/null
+++ b/docs/html/topic/performance/images/generic-timeline.png
Binary files differ
diff --git a/docs/html/topic/performance/images/moarparrots.png b/docs/html/topic/performance/images/moarparrots.png
new file mode 100644
index 0000000..ee10ec1
--- /dev/null
+++ b/docs/html/topic/performance/images/moarparrots.png
Binary files differ
diff --git a/docs/html/topic/performance/images/palette.png b/docs/html/topic/performance/images/palette.png
new file mode 100644
index 0000000..eb6be6b
--- /dev/null
+++ b/docs/html/topic/performance/images/palette.png
Binary files differ
diff --git a/docs/html/topic/performance/images/parrot.png b/docs/html/topic/performance/images/parrot.png
new file mode 100644
index 0000000..7a8b86a
--- /dev/null
+++ b/docs/html/topic/performance/images/parrot.png
Binary files differ
diff --git a/docs/html/topic/performance/images/pug-visualization.png b/docs/html/topic/performance/images/pug-visualization.png
new file mode 100644
index 0000000..8270d0e
--- /dev/null
+++ b/docs/html/topic/performance/images/pug-visualization.png
Binary files differ
diff --git a/docs/html/topic/performance/images/pugspecificdata.png b/docs/html/topic/performance/images/pugspecificdata.png
new file mode 100644
index 0000000..1c2be83
--- /dev/null
+++ b/docs/html/topic/performance/images/pugspecificdata.png
Binary files differ
diff --git a/docs/html/topic/performance/images/s-generic-closeup.png b/docs/html/topic/performance/images/s-generic-closeup.png
new file mode 100644
index 0000000..6685d51
--- /dev/null
+++ b/docs/html/topic/performance/images/s-generic-closeup.png
Binary files differ
diff --git a/docs/html/topic/performance/images/s-profiler-legend.png b/docs/html/topic/performance/images/s-profiler-legend.png
new file mode 100644
index 0000000..968fd38
--- /dev/null
+++ b/docs/html/topic/performance/images/s-profiler-legend.png
Binary files differ
diff --git a/docs/html/topic/performance/images/vq.gif b/docs/html/topic/performance/images/vq.gif
new file mode 100644
index 0000000..cbf6a35
--- /dev/null
+++ b/docs/html/topic/performance/images/vq.gif
Binary files differ
diff --git a/docs/html/topic/performance/index.jd b/docs/html/topic/performance/index.jd
index e08db15..2b6b197 100644
--- a/docs/html/topic/performance/index.jd
+++ b/docs/html/topic/performance/index.jd
@@ -1,4 +1,4 @@
-page.title=Performance
+page.title=Performance and Power
page.article=true
page.metaDescription=Improve your app's performance by learning how to optimize power consumption, launch times, and other important areas of performance.
diff --git a/docs/html/topic/performance/memory-overview.jd b/docs/html/topic/performance/memory-overview.jd
new file mode 100644
index 0000000..58067d2
--- /dev/null
+++ b/docs/html/topic/performance/memory-overview.jd
@@ -0,0 +1,288 @@
+page.title=Overview of Android Memory Management
+page.tags=ram,memory,paging,mmap
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol class="nolist">
+ <li><a href="#gc">Garbage collection</a></li>
+ <li><a href="#SharingRAM">Sharing Memory</a></li>
+ <li><a href="#AllocatingRAM">Allocating and Reclaiming App Memory</a></li>
+ <li><a href="#RestrictingMemory">Restricting App Memory</a></li>
+ <li><a href="#SwitchingApps">Switching Apps</a></li>
+</ol>
+<h2>See Also</h2>
+<ul>
+ <li><a href="{@docRoot}training/articles/memory.html">Manage Your App's Memory</a>
+ </li>
+ <li><a href="{@docRoot}studio/profile/investigate-ram.html">Investigating Your RAM Usage</a>
+ </li>
+</ul>
+
+</div>
+</div>
+
+<p>
+ The Android Runtime (ART) and Dalvik virtual machine use
+ <a href="http://en.wikipedia.org/wiki/Paging" class="external-link">paging</a>
+ and <a href="http://en.wikipedia.org/wiki/Memory-mapped_files" class="external-link">memory-mapping</a>
+ (mmapping) to manage memory. This means that any memory an app
+ modifies—whether by allocating
+ new objects or touching mmapped pages—remains resident in RAM and
+ cannot be paged out. The only way to release memory from an app is to release
+ object references that the app holds, making the memory available to the
+ garbage collector.
+ That is with one exception: any files
+ mmapped in without modification, such as code,
+ can be paged out of RAM if the system wants to use that memory elsewhere.
+</p>
+
+<p>
+ This page explains how Android manages app processes and memory
+ allocation. For more information about how to manage memory more efficiently
+ in your app, see
+ <a href="{@docRoot}training/articles/memory.html">Manage Your App's Memory</a>.
+</p>
+
+<!-- Section 1 #################################################### -->
+
+<h2 id="gc">Garbage collection</h2>
+
+<p>
+ A managed memory environment, like the ART or Dalvik virtual machine,
+ keeps track of each memory allocation. Once it determines
+ that a piece of memory is no longer being used by the program,
+ it frees it back to the heap, without any intervention from the programmer.
+ The mechanism for reclaiming unused memory
+ within a managed memory environment
+ is known as <i>garbage collection</i>. Garbage collection has two goals:
+ find data objects in a program that cannot be accessed in the future; and
+ reclaim the resources used by those objects.
+</p>
+
+<p>
+ Android’s memory heap is a generational one, meaning that there are
+ different buckets of allocations that it tracks,
+ based on the expected life and size of an object being allocated.
+ For example, recently allocated objects belong in the <i>Young generation</i>.
+ When an object stays active long enough, it can be promoted
+ to an older generation, followed by a permanent generation.
+</p>
+
+<p>
+ Each heap generation has its own dedicated upper limit on the amount
+ of memory that objects there can occupy. Any time a generation starts
+ to fill up, the system executes a garbage collection
+ event in an attempt to free up memory. The duration of the garbage collection
+ depends on which generation of objects it's collecting
+ and how many active objects are in each generation.
+</p>
+
+<p>
+ Even though garbage collection can be quite fast, it can still
+ affect your app's performance. You don’t generally control
+ when a garbage collection event occurs from within your code.
+ The system has a running set of criteria for determining when to perform
+ garbage collection. When the criteria are satisfied,
+ the system stops executing the process and begins garbage collection. If
+ garbage collection occurs in the middle of an intensive processing loop
+ like an animation or during music playback, it can increase processing time.
+ This increase can potentially push code execution in your app past the
+ recommended 16ms threshold for efficient and smooth frame rendering.
+</p>
+
+<p>
+ Additionally, your code flow may perform kinds of work that
+ force garbage collection events to occur
+ more often or make them last longer-than-normal.
+ For example, if you allocate multiple objects in the
+ innermost part of a for-loop during each frame of an alpha
+ blending animation, you might pollute your memory heap with a
+ lot of objects.
+ In that circumstance, the garbage collector executes multiple garbage
+ collection events and can degrade the performance of your app.
+</p>
+
+<p>
+ For more general information about garbage collection, see
+ <a href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)"
+ class="external-link">Garbage collection</a>.
+</p>
+
+<!-- Section 2 #################################################### -->
+
+<h2 id="SharingRAM">Sharing Memory</h2>
+
+<p>
+ In order to fit everything it needs in RAM,
+ Android tries to share RAM pages across processes. It
+ can do so in the following ways:
+</p>
+
+<ul>
+ <li>
+ Each app process is forked from an existing process called Zygote.
+ The Zygote process starts when the system boots and loads common
+ framework code and resources
+ (such as activity themes). To start a new app process,
+ the system forks the Zygote process then
+ loads and runs the app's code in the new process.
+ This approach allows most of the RAM pages allocated for
+ framework code and resources to be shared across all app processes.
+ </li>
+
+ <li>
+ Most static data is mmapped into a process.
+ This technique allows data to be shared
+ between processes, and also allows it to be paged
+ out when needed. Example static data include:
+ Dalvik code (by placing it in a pre-linked <code>.odex</code>
+ file for direct mmapping), app resources
+ (by designing the resource table to be a structure
+ that can be mmapped and by aligning the zip
+ entries of the APK), and traditional project
+ elements like native code in <code>.so</code> files.
+ </li>
+
+ <li>
+ In many places, Android shares the same dynamic
+ RAM across processes using explicitly allocated
+ shared memory regions (either with ashmem or gralloc).
+ For example, window surfaces use shared
+ memory between the app and screen compositor, and
+ cursor buffers use shared memory between the
+ content provider and client.
+ </li>
+</ul>
+
+<p>
+ Due to the extensive use of shared memory, determining
+ how much memory your app is using requires
+ care. Techniques to properly determine your app's
+ memory use are discussed in
+ <a href="{@docRoot}studio/profile/investigate-ram.html">Investigating Your RAM Usage</a>.
+</p>
+
+<!-- Section 3 #################################################### -->
+
+<h2 id="AllocatingRAM">Allocating and Reclaiming App Memory</h2>
+
+<p>
+ The Dalvik heap is constrained to a
+ single virtual memory range for each app process. This defines
+ the logical heap size, which can grow as it needs to
+ but only up to a limit that the system defines
+ for each app.
+</p>
+
+<p>
+ The logical size of the heap is not the same as
+ the amount of physical memory used by the heap.
+ When inspecting your app's heap, Android computes
+ a value called the Proportional Set Size (PSS),
+ which accounts for both dirty and clean pages
+ that are shared with other processes—but only in an
+ amount that's proportional to how many apps share
+ that RAM. This (PSS) total is what the system
+ considers to be your physical memory footprint.
+ For more information about PSS, see the
+ <a href="{@docRoot}studio/profile/investigate-ram.html">Investigating Your RAM Usage</a>
+ guide.
+</p>
+
+<p>
+ The Dalvik heap does not compact the logical
+ size of the heap, meaning that Android does not
+ defragment the heap to close up space. Android
+ can only shrink the logical heap size when there
+ is unused space at the end of the heap. However,
+ the system can still reduce physical memory used by the heap.
+ After garbage collection, Dalvik
+ walks the heap and finds unused pages, then returns
+ those pages to the kernel using madvise. So, paired
+ allocations and deallocations of large
+ chunks should result in reclaiming all (or nearly all)
+ the physical memory used. However,
+ reclaiming memory from small allocations can be much
+ less efficient because the page used
+ for a small allocation may still be shared with
+ something else that has not yet been freed.
+
+</p>
+
+<!-- Section 4 #################################################### -->
+
+<h2 id="RestrictingMemory">Restricting App Memory</h2>
+
+<p>
+ To maintain a functional multi-tasking environment,
+ Android sets a hard limit on the heap size
+ for each app. The exact heap size limit varies
+ between devices based on how much RAM the device
+ has available overall. If your app has reached the
+ heap capacity and tries to allocate more
+ memory, it can receive an {@link java.lang.OutOfMemoryError}.
+</p>
+
+<p>
+ In some cases, you might want to query the
+ system to determine exactly how much heap space you
+ have available on the current device—for example, to
+ determine how much data is safe to keep in a
+ cache. You can query the system for this figure by calling
+ {@link android.app.ActivityManager#getMemoryClass() }.
+ This method returns an integer indicating the number of
+ megabytes available for your app's heap.
+</p>
+
+<!-- Section 5 #################################################### -->
+
+<h2 id="SwitchingApps">Switching apps</h2>
+
+<p>
+ When users switch between apps,
+ Android keeps apps that
+ are not foreground—that is, not visible to the user or running a
+ foreground service like music playback—
+ in a least-recently used (LRU) cache.
+ For example, when a user first launches an app,
+ a process is created for it; but when the user
+ leaves the app, that process does <em>not</em> quit.
+ The system keeps the process cached. If
+ the user later returns to the app, the system reuses the process, thereby
+ making the app switching faster.
+</p>
+
+<p>
+ If your app has a cached process and it retains memory
+ that it currently does not need,
+ then your app—even while the user is not using it—
+ affects the system's
+ overall performance. As the system runs low on memory,
+ it kills processes in the LRU cache
+ beginning with the process least recently used. The system also
+ accounts for processes that hold onto the most memory
+ and can terminate them to free up RAM.
+</p>
+
+<p class="note">
+ <strong>Note:</strong> When the system begins killing processes in the
+ LRU cache, it primarily works bottom-up. The system also considers which
+ processes consume more memory and thus provide the system
+ more memory gain if killed.
+ The less memory you consume while in the LRU list overall,
+ the better your chances are
+ to remain in the list and be able to quickly resume.
+</p>
+
+<p>
+ For more information about how processes are cached while
+ not running in the foreground and how
+ Android decides which ones
+ can be killed, see the
+ <a href="{@docRoot}guide/components/processes-and-threads.html">Processes and Threads</a>
+ guide.
+</p>
diff --git a/docs/html/topic/performance/memory.jd b/docs/html/topic/performance/memory.jd
new file mode 100644
index 0000000..ef1c4ae
--- /dev/null
+++ b/docs/html/topic/performance/memory.jd
@@ -0,0 +1,593 @@
+page.title=Manage Your App's Memory
+page.tags=ram,low memory,OutOfMemoryError,onTrimMemory
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#monitor">Monitor Available Memory and Memory Usage</a>
+ <ul>
+ <li><a href="#AnalyzeRam">Tools for analyzing RAM usage</a></li>
+ <li><a href="#release">Release memory in response to events</a></li>
+ <li><a href="#CheckHowMuchMemory">Check how much memory you should use</a></li>
+ </ul>
+ </li>
+ <li><a href="#code">Use More Efficient Code Constructs</a>
+ <ul>
+ <li><a href="#Services">Use services sparingly</a></li>
+ <li><a href="#DataContainers">Use optimized data containers</a></li>
+ <li><a href="#Abstractions">Be careful with code abstractions</a></li>
+ <li><a href="#NanoProto">Use nano protobufs for serialized data</a></li>
+ <li><a href="#churn">Avoid memory churn</a></li>
+ </ul>
+ </li>
+ <li><a href="#remove">Remove Memory-Intensive Resources and Libraries</a>
+ <ul>
+ <li><a href="#reduce">Reduce overall APK size</a></li>
+ <li><a href="#DependencyInjection">Avoid dependency injection frameworks</a></li>
+ <li><a href="#ExternalLibs">Be careful about using external libraries</a></li>
+ </ul>
+ </li>
+</ol>
+<h2>See Also</h2>
+<ul>
+ <li><a href="{@docRoot}training/articles/memory-overview.html">Overview of Android Memory Management</a>
+ </li>
+ <li><a href="{@docRoot}studio/profile/investigate-ram.html">Investigating Your RAM Usage</a>
+ </li>
+ <li><a href="{@docRoot}topic/performance/reduce-apk-size.html">Reduce APK Size</a></li>
+</ul>
+
+</div>
+</div>
+
+<!-- INTRO #################################################### -->
+
+<p>
+ Random-access memory (RAM) is a valuable
+ resource in any software development environment, but
+ it's even more valuable on a mobile operating system
+ where physical memory is often constrained.
+ Although both the Android Runtime (ART) and Dalvik virtual machine perform
+ routine garbage collection, this does not mean you can ignore
+ when and where your app allocates and releases memory.
+ You still need to avoid
+ introducing memory leaks, usually caused by holding onto
+ object references in static member variables, and
+ release any {@link java.lang.ref.Reference} objects at the appropriate
+ time as defined by
+ lifecycle callbacks.
+</p>
+
+<p>
+ This page explains how you can
+ proactively reduce memory usage within your app.
+ For more information about general
+ practices to clean up your resources when programming in Java,
+ refer to other books or online
+ documentation about managing resource references.
+ If you’re looking for information about how to
+ analyze memory in a running app, read
+ <a href="#AnalyzeRam">Tools for analyzing RAM usage</a>.
+ For more detailed information about how the Android Runtime and Dalvik
+ virtual machine manage memory, see the
+ <a href="{@docRoot}training/articles/memory-overview.html">Overview of Android Memory Management</a>.
+</p>
+
+<!-- Section 1 #################################################### -->
+
+<h2 id="monitor">Monitor Available Memory and Memory Usage</h2>
+
+<p>
+ The Android framework, Android Studio, and Android SDK
+ can help you analyze and adjust your app's memory usage.
+ The Android framework
+ exposes several APIs that allow your app to reduce its memory usage
+ dynamically during runtime. Android Studio and the Android SDK
+ contain several tools that allow you to investigate how your
+ app uses memory.
+</p>
+
+<!-- Section 1.1 #################################################### -->
+
+<h3 id="AnalyzeRam">Tools for analyzing RAM usage</h3>
+
+<p>
+ Before you can fix the memory usage problems in your app, you first need
+ to find them. Android Studio and the Android SDK include several tools
+ for analyzing memory usage in your app:
+</p>
+
+<ol>
+ <li>
+ The Device Monitor has a Dalvik Debug Monitor Server (DDMS) tool that allows
+ you to inspect memory allocation within your app process.
+ You can use this information to understand how your
+ app uses memory overall. For example, you can force a garbage collection
+ event and then view the types of objects that remain in memory. You can
+ use this information to identify operations or actions within your app
+ that allocate or leave excessive amounts of objects in memory.
+
+ <p>For more information about how to use the DDMS tool, see
+ <a href="/studio/profile/ddms.html">Using DDMS</a>.
+ </p>
+ </li>
+
+ <li>
+ The Memory Monitor in Android Studio shows you how your app allocates
+ memory over the course of a single session.
+ The tool shows a graph of available
+ and allocated Java memory over time, including garbage collection events.
+ You can also initiate garbage collection events and take a snapshot of
+ the Java heap while your app runs. The output from the Memory Monitor tool
+ can help you identify points when your app experiences excessive garbage
+ collection events, leading to app slowness.
+ <p>
+ For more information about how to use Memory Monitor tool, see
+ <a href="{@docRoot}tools/debugging/debugging-memory.html#ViewHeap">Viewing Heap Updates</a>.
+ </p>
+ </li>
+
+ <li>
+ Garbage collection events also show up in the Traceview viewer. Traceview
+ allows you to view trace log files as both a timeline and as a profile
+ of what happened within a method. You can use this tool to determine
+ what code was executing when a garbage collection event occurred.
+ <p>
+ For more information about how to use the Traceview viewer, see
+ <a href="https://developer.android.com/studio/profile/traceview.html">Profiling with Traceview and dmtracedump</a>.
+ </p>
+ </li>
+
+ <li>
+ The Allocation Tracker tool in Android Studio gives you a detailed look
+ at how your app allocates memory.
+ The Allocation Tracker records an app's memory allocations and lists
+ all allocated objects within the profiling snapshot. You can use this
+ tool to track down parts of your code that allocate too many objects.
+
+ <p>
+ For more information about how to use the Allocation Tracker tool, see
+ <a href="{docRoot}studio/profile/allocation-tracker-walkthru.html">Allocation Tracker Walkthrough</a>.
+ </p>
+ </li>
+
+</ol>
+
+<!-- Section 1.2 #################################################### -->
+
+<h3 id="release">Release memory in response to events</h3>
+
+<p>
+ An Android device can run with varying amounts of free memory
+ depending on the physical amount of RAM on the device and how the user
+ operates it. The system broadcasts signals to indicate when it is under
+ memory pressure, and apps should listen for these signals and adjust
+ their memory usage as appropriate.
+</p>
+
+</p>
+ You can use the {@link android.content.ComponentCallbacks2} API
+ to listen for these signals and then adjust your memory
+ usage in response to app lifecycle
+ or device events. The
+ {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()}
+ method allows your app to listen for memory related events when the app runs
+ in the foreground (is visible) and when it runs in the background.
+</p>
+
+<p>
+ To listen for these events, implement the {@link
+ android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()}
+ callback in your {@link android.app.Activity}
+ classes, as shown in the following code snippet.
+</p>
+
+<pre class="prettyprint">
+import android.content.ComponentCallbacks2;
+// Other import statements ...
+
+public class MainActivity extends AppCompatActivity
+ implements ComponentCallbacks2 {
+
+ // Other activity code ...
+
+ /**
+ * Release memory when the UI becomes hidden or when system resources become low.
+ * @param level the memory-related event that was raised.
+ */
+ public void onTrimMemory(int level) {
+
+ // Determine which lifecycle or system event was raised.
+ switch (level) {
+
+ case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
+
+ /*
+ Release any UI objects that currently hold memory.
+
+ The user interface has moved to the background.
+ */
+
+ break;
+
+ case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
+ case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
+ case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
+
+ /*
+ Release any memory that your app doesn't need to run.
+
+ The device is running low on memory while the app is running.
+ The event raised indicates the severity of the memory-related event.
+ If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
+ begin killing background processes.
+ */
+
+ break;
+
+ case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
+ case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+ case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+
+ /*
+ Release as much memory as the process can.
+
+ The app is on the LRU list and the system is running low on memory.
+ The event raised indicates where the app sits within the LRU list.
+ If the event is TRIM_MEMORY_COMPLETE, the process will be one of
+ the first to be terminated.
+ */
+
+ break;
+
+ default:
+ /*
+ Release any non-critical data structures.
+
+ The app received an unrecognized memory level value
+ from the system. Treat this as a generic low-memory message.
+ */
+ break;
+ }
+ }
+}
+</pre>
+
+<p>
+ The
+ {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()}
+ callback was added in Android 4.0 (API level 14). For earlier versions,
+ you can use the
+ {@link android.content.ComponentCallbacks#onLowMemory()}
+ callback as a fallback for older versions, which is roughly equivalent to the
+ {@link android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE} event.
+</p>
+
+<!-- Section 1.3 #################################################### -->
+
+<h3 id="CheckHowMuchMemory">Check how much memory you should use</h3>
+
+<p>
+ To allow multiple running processes, Android sets a hard limit
+ on the heap size alloted for each app. The exact heap size limit varies
+ between devices based on how much RAM the device
+ has available overall. If your app has reached the heap capacity and
+ tries to allocate more
+ memory, the system throws an {@link java.lang.OutOfMemoryError}.
+</p>
+
+<p>
+ To avoid running out of memory, you can to query the system to determine
+ how much heap space you have available on the current device.
+ You can query the system for this figure by calling
+ {@link android.app.ActivityManager#getMemoryInfo(android.app.ActivityManager.MemoryInfo) getMemoryInfo()}.
+ This returns an
+ {@link android.app.ActivityManager.MemoryInfo } object that provides
+ information about the device's
+ current memory status, including available memory, total memory, and
+ the memory threshold—the memory level below which the system begins
+ to kill processes. The
+ {@link android.app.ActivityManager.MemoryInfo } class also exposes a simple
+ boolean field,
+ {@link android.app.ActivityManager.MemoryInfo#lowMemory }
+ that tells you whether the device is running low on memory.
+</p>
+
+<p>
+ The following code snippet shows an example of how you can use the
+ {@link android.app.ActivityManager#getMemoryInfo(android.app.ActivityManager.MemoryInfo) getMemoryInfo()}.
+ method in your application.
+</p>
+
+<pre class="prettyprint">
+public void doSomethingMemoryIntensive() {
+
+ // Before doing something that requires a lot of memory,
+ // check to see whether the device is in a low memory state.
+ ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
+
+ if (!memoryInfo.lowMemory) {
+ // Do memory intensive work ...
+ }
+}
+
+// Get a MemoryInfo object for the device's current memory status.
+private ActivityManager.MemoryInfo getAvailableMemory() {
+ ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+ activityManager.getMemoryInfo(memoryInfo);
+ return memoryInfo;
+}
+</pre>
+
+<!-- Section 2 #################################################### -->
+
+<h2 id="code">Use More Memory-Efficient Code Constructs</h2>
+
+<p>
+ Some Android features, Java classes, and code constructs tend to
+ use more memory than others. You can minimize how
+ much memory your app uses by choosing more efficient alternatives in
+ your code.
+</p>
+
+<!-- Section 2.1 #################################################### -->
+
+<h3 id="Services">Use services sparingly</h3>
+
+<p>
+ Leaving a service running when it’s not needed is
+ <strong>one of the worst memory-management
+ mistakes</strong> an Android app can make. If your app needs a
+ <a href="{@docRoot}guide/components/services.html">service</a>
+ to perform work in the background, do not keep it running unless
+ it needs to run a job. Remember to stop your service when it has completed
+ its task. Otherwise, you can inadvertently cause a memory leak.
+</p>
+
+<p>
+ When you start a service, the system prefers to always keep the process
+ for that service running. This behavior
+ makes services processes very expensive
+ because the RAM used by a service remains unavailable to other processes.
+ This reduces the number of cached processes that the system can keep in
+ the LRU cache, making app switching less efficient. It can even lead to
+ thrashing in the system when memory is tight and the system can’t
+ maintain enough processes to host all the services currently running.
+</p>
+
+<p>
+ You should generally avoid use of persistent services because of
+ the on-going demands they place on available memory. Instead, we
+ recommend that you use an alternative implementation
+ such as {@link android.app.job.JobScheduler}. For more information about
+ how to use {@link android.app.job.JobScheduler} to schedule background
+ processes, see
+ <a href="/topic/performance/background-optimization.html">Background Optimizations</a>.
+<p>
+ If you must use a service, the
+ best way to limit the lifespan of your service is to use an {@link
+ android.app.IntentService}, which finishes
+ itself as soon as it's done handling the intent that started it.
+ For more information, read
+ <a href="{@docRoot}training/run-background-service/index.html">Running in a Background Service</a>.
+</p>
+
+<!-- Section 2.2 #################################################### -->
+
+<h3 id="DataContainers">Use optimized data containers</h3>
+
+<p>
+ Some of the classes provided by the programming language are not optimized for
+ use on mobile devices. For example, the generic
+ {@link java.util.HashMap} implementation can be quite memory
+ inefficient because it needs a separate entry object for every mapping.
+</p>
+
+<p>
+ The Android framework includes several optimized data containers, including
+ {@link android.util.SparseArray}, {@link android.util.SparseBooleanArray},
+ and {@link android.support.v4.util.LongSparseArray}.
+ For example, the {@link android.util.SparseArray} classes are more
+ efficient because they avoid the system's need to
+ <acronym title="Automatic conversion from primitive types to object classes (such as int to Integer)">autobox</acronym>
+ the key and sometimes value (which creates yet another object or
+ two per entry).
+</p>
+
+<p>
+ If necessary, you can always switch to raw arrays for a really lean data
+ structure.
+</p>
+
+<!-- Section 2.3 #################################################### -->
+
+<h3 id="Abstractions">Be careful with code abstractions</h3>
+
+<p>
+ Developers often use abstractions simply as a good programming practice,
+ because abstractions can improve code flexibility and maintenance.
+ However, abstractions come at a significant cost:
+ generally they require a fair amount more code that
+ needs to be executed, requiring more time and
+ more RAM for that code to be mapped into memory.
+ So if your abstractions aren't supplying a
+ significant benefit, you should avoid them.
+</p>
+
+<p>
+ For example, enums often require more than twice as much memory as static
+ constants. You should strictly avoid using enums on Android.
+</p>
+
+<!-- Section 2.4 #################################################### -->
+
+<h3 id="NanoProto">Use nano protobufs for serialized data</h3>
+
+<p>
+ <a href="https://developers.google.com/protocol-buffers/docs/overview">Protocol buffers</a>
+ are a language-neutral, platform-neutral, extensible mechanism
+ designed by Google for serializing structured data—similar to XML, but
+ smaller, faster, and simpler. If you decide to use
+ protobufs for your data, you should always use nano protobufs in your
+ client-side code. Regular protobufs generate extremely verbose code, which
+ can cause many kinds of problems in your app such as
+ increased RAM use, significant APK size increase, and slower execution.
+</p>
+
+<p>
+ For more information, see the "Nano version" section in the
+ <a href="https://android.googlesource.com/platform/external/protobuf/+/master/java/README.txt"
+class="external-link">protobuf readme</a>.
+</p>
+
+<!-- Section 2.5 #################################################### -->
+
+<h3 id="churn">Avoid memory churn</h3>
+
+<p>
+ As mentioned previously, garbage collections events don't normally affect
+ your app's performance. However, many garbage collection events that occur
+ over a short period of time can quickly eat up your frame time. The more time
+ that the system spends on garbage collection, the less time it has to do
+ other stuff like rendering or streaming audio.
+</p>
+
+<p>
+ Often, <em>memory churn</em> can cause a large number of
+ garbage collection events to occur. In practice, memory churn describes the
+ number of allocated temporary objects that occur in a given amount of time.
+</p>
+
+<p>
+ For example, you might allocate multiple temporary objects within a
+ <code>for</code> loop. Or you might create new
+ {@link android.graphics.Paint} or {@link android.graphics.Bitmap}
+ objects inside the
+ {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()}
+ function of a view.
+ In both cases, the app creates a lot of objects quickly at high volume.
+ These can quickly consume all the available memory in the young generation,
+ forcing a garbage collection event to occur.
+</p>
+
+<p>
+ Of course, you need to find the places in your code where
+ the memory churn is high before you can fix them. Use the tools discussed in
+ <a href="#AnalyzeRam">Analyze your RAM usage</a>
+</p>
+
+<p>
+ Once you identify the problem areas in your code, try to reduce the number of
+ allocations within performance critical areas. Consider moving things out of
+ inner loops or perhaps moving them into a
+ <a href="https://en.wikipedia.org/wiki/Factory_method_pattern" class="external-link">Factory</a>
+ based allocation structure.
+</p>
+
+<!-- Section 3 #################################################### -->
+
+<h2 id="remove">Remove Memory-Intensive Resources and Libraries</h2>
+
+<p>
+ Some resources and libraries within your code can gobble up memory without
+ you knowing it. Overall size of your APK, including third-party libraries
+ or embedded resources, can affect how much memory your app consumes. You can
+ improve your app's memory consumption by removing any redundant, unnecessary,
+ or bloated components, resources, or libraries from your code.
+</p>
+
+<!-- Section 3.1 #################################################### -->
+
+<h3 id="reduce">Reduce overall APK size</h3>
+
+<p>
+ You can significantly reduce your app's memory usage by reducing the overall
+ size of your app. Bitmap size, resources, animation frames, and third-party
+ libraries can all contribute to the size of your APK.
+ Android Studio and the Android SDK provide multiple tools
+ to help you reduce the size of your resources and external dependencies.
+</p>
+
+<p>
+ For more information about how to reduce your overall APK size, see
+ <a href="{@docRoot}topic/performance/reduce-apk-size.html">Reduce APK Size</a>.
+</p>
+
+<!-- Section 3.2 #################################################### -->
+
+<h3 id="DependencyInjection">Use caution with dependency injection frameworks</h3>
+
+<p>
+ Dependency injection framework such as
+ <a href="https://code.google.com/p/google-guice/" class="external-link">Guice</a>
+ or
+ <a href="https://github.com/roboguice/roboguice" class="external-link">RoboGuice</a>
+ can simplify the code you write and provide an adaptive environment
+ that's useful for testing and other configuration changes. However, dependency
+ frameworks aren't always optimized for mobile devices.
+</p>
+
+<p>
+ For example, these frameworks tend to initialize processes by
+ scanning your code for annotations. This which can require significant
+ amounts of your code to be mapped into RAM unnecessarily. The system
+ allocates these mapped pages into clean memory so Android can drop them; yet
+ that can't happen until the pages have remained in memory for a long period
+ of time.
+ </p>
+
+<p>
+ If you need to use a dependency injection framework in your app, consider
+ using
+ <a class="external-link" href="http://google.github.io/dagger/">Dagger</a>
+ instead. For example, Dagger does not use reflection to scan your app's code.
+ Dagger's strict implementation means that it can be used in Android apps
+ without needlessly increasing memory usage.
+</p>
+
+<!-- Section 3.3 #################################################### -->
+
+<h3 id="ExternalLibs">Be careful about using external libraries</h3>
+
+<p>
+ External library code is often not written for mobile environments and
+ can be inefficient when used
+ for work on a mobile client. When you decide to use an
+ external library, you may need to optimize that library for mobile devices.
+ Plan for that work up-front and analyze the library in terms of code size and
+ RAM footprint before deciding to use it at all.
+</p>
+
+<p>
+ Even some mobile-optimized libraries can cause problems due to differing
+ implementations. For example, one library may use nano protobufs
+ while another uses micro protobufs, resulting in two different protobuf
+ implementations in your app. This can happen with different
+ implementations of logging, analytics, image loading frameworks,
+ caching, and many other things you don't expect.
+</p>
+
+<p>
+ Although <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> can
+ help to remove APIs and resources with the right flags, it can't remove a
+ library's large internal dependencies. The features that you want in these
+ libraries may require lower-level dependencies. This becomes especially
+ problematic when you use an {@link android.app.Activity } subclass from a
+ library (which will tend to have wide swaths of dependencies),
+ when libraries use reflection (which is common and means you need to spend a
+ lot of time manually tweaking ProGuard to get it to work), and so on.
+</p>
+
+<p>
+ Also avoid using a shared library for just one or two features out of dozens.
+ You don't want to pull in a large amount of code and overhead that
+ you don't even use. When you consider whether to use a library, look for
+ an implementation that strongly matches what you need. Otherwise, you might
+ decide to create your own implementation.
+</p>
+
diff --git a/docs/html/topic/performance/network-xfer.jd b/docs/html/topic/performance/network-xfer.jd
new file mode 100644
index 0000000..7fe5594
--- /dev/null
+++ b/docs/html/topic/performance/network-xfer.jd
@@ -0,0 +1,374 @@
+page.title=Reducing Image Download Sizes
+page.metaDescription=Improve network performance by optimizing image size.
+
+meta.tags="performance"
+page.tags="performance"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+ <ol>
+
+ <li>
+ <a href="#uif">Understanding Image Formats</a>
+ <ul>
+ <li><a href="#png">PNG</a></li>
+ <li><a href="#jpg">JPG</a></li>
+ <li><a href="#webp">WebP</a></li>
+ </ul>
+ </li>
+ <li>
+ <a href="#sf">Selecting a Format</a></li>
+ <li><a href="#doqv">Determining Optimal Quality Values</a>
+ <ul>
+ <li><a href="#sv">Scalar Values (JPG, WebP only)</a></li>
+ <li><a href="#butter">Butteraugli</a></li>
+ </ul>
+ </li>
+ <li><a href="#sizes">Serving Sizes</a></li>
+ </ol>
+ </div>
+</div>
+
+<p>
+Most download traffic consists of images. As a result, the smaller you can make
+your downloadable images, the better a network experience your app can provide
+for users. This page provides guidance on making image files smaller and more
+network-friendly.
+</p>
+
+<h2 id="uif">Understanding Image Formats</h2>
+
+<p>Android apps typically use images that are in one or more of the following file
+formats: PNG, JPG, and WebP. For each of these formats, there are steps you can
+take to reduce image sizes.
+</p>
+
+<h3 id="png">PNG</h3>
+
+<p>
+A key to making your PNG files smaller is reducing the number of unique
+colors used in each row of pixels that comprises the image. By using fewer
+colors, you improve the compression potential at all of the other stages of
+the pipeline.
+</p>
+
+<p>
+Reducing the number of unique colors makes a significant difference because PNG
+compression effectiveness is partly a function of the degree to which
+horizontally adjacent pixel colors vary. Thus, reducing the number of unique
+colors in each row of your PNG images can help in reducing their file sizes.
+</p>
+
+<p>
+When deciding whether to pursue this strategy, you should keep in mind that
+reducing the number of unique colors effectively amounts to applying a lossy
+encoding stage to the image. However, an encoding tool may not be a good
+judge of how bad a seemingly small error looks to the human eye. Therefore,
+you should perform this work manually in order to help ensure
+the right balance between efficient compression and acceptable image quality.
+</p>
+
+<p>
+There are two particularly useful approaches you can take: striving for indexed
+formats, and applying vector quantization.
+</p>
+
+
+<h4 id="strive">Strive for indexed formats</h4>
+
+<p>
+Any attempt at color reduction should start with trying to optimize your colors
+so that you can use the INDEXED format when exporting the image as a PNG. The
+INDEXED color mode works by choosing the best 256 colors to use, and replacing
+all pixel values with indices into that color palette. The result is a
+reduction from 16 million (potential) colors to only 256 colors: from 3 (without
+transparency) or 4 (with transparency) bytes per pixel to 1 byte per pixel.
+This change is a significant first-step file size reduction.
+</p>
+
+<p>
+Figure 1 shows shows an image and its indexed variant.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/beforeafterindexed.png">
+ <p class="img-caption">
+Figure 1. An image before and after conversion to the INDEXED format.
+ </p>
+
+
+<p>
+Figure 2 shows the color palette for the image in Figure 1:
+</p>
+
+ <img src="{@docRoot}topic/performance/images/palette.png">
+ <p class="img-caption">
+Figure 2. The color palette for the image in Figure 1.
+ </p>
+
+<p>
+Representing your image as a paletted image goes a long way toward
+significantly improving the file size, so it's worth investigating if the
+majority of your images can be converted.
+</p>
+
+<p>
+Of course, not every image can be accurately represented with only 256 colors.
+Some images, for example, might need 257, 310, 512, or 912 colors to
+look correct. In such cases, vector quantization can also be helpful.
+</p>
+
+<h4 id="vq">Vector quantization</h4>
+
+<p>
+The process of creating an indexed image may be better described as vector
+quantization (VQ). VQ serves as a rounding process for multidimensional
+numbers. In this process, all the colors in your image get grouped based upon
+their similarity. For a given group, all colors in that group are replaced by a
+single <em>center point</em> value, which minimizes error for colors in that
+cell (or "site" if you're using the Voronoi terminology). In Figure 3,
+the green dots represent input colors, and the red dots are the center points
+that replace the input colors. Each cell is bounded by blue lines.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/vq.gif">
+ <p class="img-caption">
+Figure 3. Applying vector quantization to the colors in an image.
+</p>
+
+<p>
+The result of applying VQ to an image reduces the number of unique colors,
+replacing each group of colors with a single color that's "pretty close"
+in visual quality.
+</p>
+
+<p>
+This technique also allows you to define the maximum number of unique colors in
+your image. For example, Figure 4 shows the a parrot head in 16.7 million colors
+(24 bits per pixel, or bpp) alongside a version that only allows only
+16 (3 bpp) unique colors to be used.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/parrot.png">
+ <p class="img-caption">
+Figure 4. Image before and after application of vector quantification.
+ </p>
+
+<p>
+Immediately, you can see that there's a loss of quality; most of the gradient
+colors have been replaced, imparting a banding effect to the image. This image
+needs more than 16 unique colors.
+</p>
+
+<p>
+Setting up a VQ step in your pipeline can help you get a better sense of the
+true number of unique colors that your image uses, and can help you reduce them
+significantly. There are a number of readily available tools that you can use
+to help you implement this technique.
+</p>
+
+<h3 id="jpg">JPG</h3>
+
+<p>
+If you are using JPG images, there are several small changes you can make that
+potentially provide significant file-size savings. These include:
+</p>
+
+<ul>
+ <li>
+Producing a smaller file size through different encoding methods (without
+impacting quality).
+ </li>
+
+ <li>
+Adjusting quality slightly in order to yield better compression.
+ </li>
+</ul>
+
+<p>Pursuing these strategies can often net you file-size reductions of up to
+25%.
+</p>
+
+<p>
+When choosing tools, remember that photo exporting tools can
+insert unnecessary metadata, such as GPS information, into your images. At
+a minimum, try to leverage existing tools to help strip out this information
+from your files.
+</p>
+
+<h3 id="webp">WebP</h3>
+
+<p>
+WebP is a newer image format supported from Android 4.2.1 (API level 17). This
+format provides superior lossless and lossy compression for images on the web.
+Using WebP, developers can create smaller, richer images. WebP lossless image
+files are, on average,
+<a href="https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study#conclusions">
+26% smaller</a> than PNGs. These image files also support
+transparency (also known as alpha channel) at a cost of just
+<a href="https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study#results">
+22% more</a> bytes.
+</p>
+
+<p>
+WebP lossy images are
+<a href="https://developers.google.com/speed/webp/docs/webp_study#experiment_1_webp_vs_jpeg_at_equal_ssim_index">
+25-34% smaller</a> than comparable JPG images at equivalent
+<a href="https://en.wikipedia.org/wiki/Structural_similarity">SSIM</a>
+quality indices. For cases when lossy RGB compression is acceptable, lossy
+WebP also supports transparency, typically producing file sizes 3 times smaller
+than PNG.
+</p>
+
+<p>
+For more information about WebP, visit the
+<a href="https://developers.google.com/speed/webp/">WebP site</a>.
+</p>
+
+<h2 id="sf">Selecting a Format</h2>
+
+<p>
+Different image formats are suitable for different types of images. JPG and PNG
+have very different compression processes, and they produce quite different
+results.
+</p>
+
+<p>
+The decision between PNG and JPG often comes down to the complexity of the
+image itself. Figure 5 shows two images that come out quite differently
+depending on which compression scheme the developer applies. The image on the
+left has many small details, and thus compresses more efficiently with JPG. The
+image on the right, with runs of the same color, compresses more efficiently
+with PNG.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/comparison.png">
+ <p class="img-caption">
+Figure 5. Suitable cases for JPG vs. PNG
+ </p>
+
+
+<p>
+WebP as a format can support both lossy and lossless modes, making it an ideal
+replacement for both PNG and JPG. The only thing to keep in mind is that it
+only has native support on devices running Android 4.2.1 (API level 17) and
+higher. Fortunately, the large
+<a
+href="https://developer.android.com/about/dashboards/index.html#Platform">
+majority of devices</a> satisfy that requirement.
+</p>
+
+<p>
+Figure 6 provides a simple visualization to help you decide which compression
+scheme to use.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/decisions.png">
+ <p class="img-caption">
+Figure 6. Deciding on a compression scheme
+ </p>
+
+<h2 id="doqv">Determining Optimal Quality Values</h2>
+
+<p>
+There are several techniques you can use to achieve the right balance between
+compression and image quality. One technique uses scalar values and therefore
+only works for JPG and WebP. The other technique takes advantage of the
+Butteraugli library, and is usable for all image formats.
+</p>
+
+<h3 id="sv">Scalar values (JPG and WebP only)</h3>
+
+<p>
+The power of JPG and WebP comes from the fact that you can use a scalar value
+to balance quality against file size. The trick is finding out what the correct
+quality value is for your image. Too low a quality level produces a small file
+at the cost of image quality. Too high a quality level increases file size
+without providing a noticeable benefit to the user.
+</p>
+
+<p>
+The most straightforward solution is to pick some non-maximum value, and use
+that value. However, be aware that the quality value affects every image
+differently. While a quality of 75%, for example, may look fine on most images,
+there may be some cases do not fare as well. You should make sure to test your
+chosen maximum value against a representative sample of images. Also, make
+sure to perform all of your tests against the original images, and not on
+compressed versions.
+</p>
+
+<p>
+For large media applications that upload and re-send millions of JPGs a day,
+hand-tuning for each asset is impractical. You might address this challenge by
+specifying several different quality levels, according to image category. For
+example, you might set 35% as the quality setting for thumbnails, since a
+smaller image hides more compression artifacts.
+</p>
+
+<h3 id="butter">Butteraugli</h4>
+
+<p>
+The Butteraugli project is a library to test an image's Psychovisual Error
+Threshold: the point at which a viewer starts to notice image degradation. In
+other words, this project attempts to quantify how distorted your compressed
+image is.
+</p>
+
+<p>
+Butteraugli allows you to define a goal for visual quality, and then run PNG,
+JPG, WebP lossy, and WebP lossless compressions. You can then choose the image
+that is the best balance of file size and Butteraugli level. Figure 7 shows an
+example of how Butteraugli was used to find the minimal JPG quality level
+before the visual distortion was high enough for a user could perceive a
+problem; the result is a roughly 65% reduction in file size.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/moarparrots.png">
+ <p class="img-caption">
+Figure 7. An image before and after application of Butteraugli technology.
+ </p>
+
+<p>
+Butteraugli allows you to proceed based on either output or input. That is, you
+can look for the lowest quality setting before a user perceives noticeable
+distortion in the resulting image, or you can iteratively set image-distortion
+levels to learn their associated quality levels.
+</p>
+
+<h2 id="sizes">Serving Sizes</h2>
+
+<p>
+It is tempting to keep only a single resolution of an image on a server. When a
+device accesses the image, the server serves it at that one resolution and
+leaves downscaling to the device.
+</p>
+
+<p>
+This solution is convenient for the developer, but potentially painful for the
+user, because the solution forces the user to download much more data than they
+need.
+
+You should instead store multiple sizes of images, and serve the size that is
+most appropriate for a particular use case. For example, for a thumbnail,
+serving an actual thumbnail image instead of serving and downscaling a
+full-size version consumes much less network bandwidth
+</p>
+
+</p>
+This approach is good for download speed, and is less costly for users who may
+be using limited or metered data plans. Proceeding like this also results in
+the image's taking less space on the device and in main memory. In the
+case of large images, such as 4K ones, this approach also saves the device
+from having to resize images before loading them.
+</p>
+
+<p>
+Implementing this approach requires that you have a backend image service to
+provide images at various resolutions with proper caching. There are existing
+services that can provide help with this task. For example,
+<a href="https://cloud.google.com/appengine/">App Engine</a> comes
+with image resizing functionality already installed.
+</p>
diff --git a/docs/html/topic/performance/power/battery-historian.jd b/docs/html/topic/performance/power/battery-historian.jd
new file mode 100644
index 0000000..79ea59d
--- /dev/null
+++ b/docs/html/topic/performance/power/battery-historian.jd
@@ -0,0 +1,247 @@
+page.title=Analyzing Power Use with Battery Historian
+page.metaDescription=Improve network performance by optimizing image size.
+
+meta.tags="power"
+page.tags="power"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+ <ol>
+ <li>
+ <a href="#sv">System-wide View</a>
+ </li>
+ <li>
+ <a href="#asd">App-Specific Data</a>
+ </li>
+ <li>
+ <a href="#usecases">Other Cases Where Battery Historian Can Help</a>
+ </li>
+ </ol>
+<h2>See also</h2>
+ <ol>
+ <li>
+ <a href="https://github.com/google/battery-historian">Battery Historian
+ on GitHub</a>
+ </li>
+
+ <li>
+ <a href="https://developer.android.com/studio/profile/battery-historian.html">
+ Batterystats and Battery Historian Walkthrough
+ </li>
+
+ <li>
+ <a href="https://youtu.be/VC2Hlb22mZM?list=PLOU2XLYxmsILe6_eGvDN3GyiodoV3qNSC&t=2063"
+ target="_blank">
+ Battery Historian talk at Google I/O 2016</a>
+ </li>
+ </ol>
+ </div>
+</div>
+
+<p>
+The Battery Historian tool provides insight into a device’s battery consumption
+over time. At a system-wide level, the tool visualizes power-related events from
+the system logs in an HTML representation. At an app-specific level, the tool
+provides a variety of data that can help you identify battery-draining app
+behavior.
+</p>
+
+<p>
+This document describes some of the ways you can use Battery Historian
+to learn about battery-consumption patterns. The document begins by explaining
+how to read the system-wide data that Battery Historian reports. Then,
+it presents ways in which you can use Battery Historian to diagnose
+and troubleshoot your own app's behavior related to battery consumption.
+Last, it offers several tips on scenarios in which Battery Historian may be
+particularly useful.
+</p>
+
+<h2 id="sv">System-wide View</h2>
+
+<p>
+The Battery Historian tool provides a system-wide visualization of various
+app and system behaviors, along with their correlation against battery
+consumption over time. This view, shown in Figure 1, can help you
+diagnose and identify power use issues with your app.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/generic-timeline.png">
+ <p class="img-caption">
+<strong>Figure 1.</strong>
+Battery Historian’s display of system-wide events affecting power
+consumption.
+ </p>
+
+<p>
+Of particular interest in this figure is the black, horizontal, downward trend
+line representing Battery Level, measured on the y-axis. For example, at the
+very beginning of the Battery Level line, at approximately 6:50 AM, the
+visualization shows a relatively steep drop in battery level.
+</p>
+
+<p>
+Figure 2 provides a close-up of that part of the display.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/s-generic-closeup.png">
+ <p class="img-caption">
+<strong>Figure 2.</strong>
+A close-up of the Battery Historian timeline from roughly 6:50 AM to 7:20 AM.
+ </p>
+
+<p>
+At the very beginning of the Battery Level line, as battery decline steeply,
+the display shows three things happening: The CPU is running, an app has
+acquired a wakelock, and the screen is on. In this way, Battery Historian helps
+you understand what events are happening when battery consumption is high. You
+can then target these behaviors in your app and investigate whether there are
+related optimizations you can make.
+</p>
+
+<p>
+The system-wide visualization can provide other clues, as well. For instance, if
+it shows that the mobile radio is frequently being turned off and on, there may
+be an opportunity to optimize this behavior through <a href=”intelligent
+scheduling page”>intelligent scheduling APIs</a> such as JobScheduler or
+Firebase Job Dispatcher.
+</p>
+
+<p>
+The next section explains how to investigate behavior and events specific to
+your own app.
+</p>
+
+<p>
+<h2 id="asd">App-Specific Data</h2>
+</p>
+
+<p>
+In addition to the macro-level data provided by the system-wide view, Battery
+Historian also provides tables and some visualization of data specific to each
+app running on your device. The tabular data includes:
+</p>
+
+<ul>
+ <li>The app’s estimated power use on the device.</li>
+ <li>Network information.</li>
+ <li>Wakelocks.</li>
+ <li>Services.</li>
+ <li>Process info.</li>
+</ul>
+
+<p>
+The tables provide two dimensions of data about your app. First, you can look
+up where your app’s power usage ranks compared to other apps. To do so, click
+<em>Device Power Estimates</em> table under <em>Tables</em>. This example
+examines a fictional app called Pug Power.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/app-rankings.png">
+ <p class="img-caption">
+<strong>Figure 3.</strong> Investigating which apps consume the most power.
+ </p>
+
+<p>
+The table in Figure 3 reveals that Pug Power is the ninth biggest consumer of
+battery power on this device, and the third biggest app that is not part of the
+OS. This data suggests that this app bears deeper investigation.
+</p>
+
+<p>
+To look up the data for a specific app, enter its package name into the lower
+of the two dropdown menus under <em>App Selection</em>, located under the left
+side of the visualization.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/dropdown.png">
+ <p class="img-caption">
+<strong>Figure 4.</strong> Entering a specific app whose data to view.
+ </p>
+
+<p>
+When you select a specific app, the following data visualization categories
+change to display app-specific data instead of system-wide data:
+</p>
+
+<ul>
+ <li>SyncManager.</li>
+ <li>Foreground process.</li>
+ <li>Userspace Wakelock.</li>
+ <li>Top app.</li>
+ <li>JobScheduler.</li>
+ <li>Activity Manager Proc.</li>
+</ul>
+
+The SyncManager and JobScheduler visualizations immediately make it obvious if
+your app performs syncs and executes jobs more frequently than necessary. In
+doing so, they can quickly reveal an opportunity to optimize your app’s
+behavior for improved battery performance.
+
+<p>
+You can also obtain one more piece of app-specific visualization data,
+<em>Userspace Wakelock</em>. To include this information in the bug report,
+enter the following command in your terminal window:
+</p>
+
+<pre>
+$ adb shell dumpsys batterystats --enable full-wake-history
+</pre>
+
+<p class="note">
+<strong>Note:</strong> From Android 6.0 (API level 23), the platform includes
+Doze functionality, which imposes certain optimizations on apps. For example,
+Doze batches jobs to take place during brief maintenance windows, regardless of
+how JobScheduler has scheduled them.
+</p>
+
+<p>
+Figures 5 and 6 show data for Pug Power: Figure 5
+shows the visualization of
+the app-specific data, and Figure 6 shows the corresponding tabular data.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/pug-visualization.png">
+ <p class="img-caption">
+<strong>Figure 5.</strong> Visualization of data for fictional app Pug Power.
+ </p>
+
+ <img src="{@docRoot}topic/performance/images/pugspecificdata.png">
+ <p class="img-caption">
+<strong>Figure 6.</strong> Tabular data for the fictional Pug Power app.
+ </p>
+
+<p>
+A look at the visualization does not show anything immediately obvious.
+The JobScheduler line shows that the app has no jobs scheduled. The SyncManager
+line shows that the app has not performed any syncs.
+</p>
+
+<p>
+However, examination of the <em>Wakelocks</em> segment of the tabular data
+reveals that Pug Power acquires wakelocks totaling over an hour. This unusual
+and costly behavior can account for the app’s high level of power consumption.
+This piece of information helps the developer target an area where optimization
+is likely to greatly help. In this case, why does the app acquire so much
+wakelock time, and how can the developer ameliorate this behavior?
+</p>
+
+<h2 id="usecases">Other Cases Where Battery Historian Can Help</h2>
+
+<p>
+There are many other cases in which Battery Historian can help you diagnose
+opportunities for improving battery behavior. For example, Battery Historian
+can tell you if your app is:
+</p>
+
+<ul>
+ <li>Firing wakeup alarms overly frequently (every 10 seconds or less).</li>
+ <li>Continuously holding a GPS lock.</li>
+ <li>Scheduling jobs every 30 seconds or less.</li>
+ <li>Scheduling syncs every 30 seconds or less.</li>
+ <li>Using the cellular radio more frequently than you expect.</li>
+</ul>
+
diff --git a/docs/html/topic/performance/power/index.jd b/docs/html/topic/performance/power/index.jd
new file mode 100644
index 0000000..88addce
--- /dev/null
+++ b/docs/html/topic/performance/power/index.jd
@@ -0,0 +1,125 @@
+page.title=Optimizing for Battery Life
+page.metaDescription=Learn how to help your app go easier on the battery.
+
+meta.tags="performance"
+page.tags="performance"
+
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+ <h2>
+ In this document
+ </h2>
+ <ol>
+ <li>
+ <a href="#lazy">Lazy First</a>
+ </li>
+ <li>
+ <a href="#features">Platform Features</a>
+ </li>
+ <li>
+ <a href="#toolery">Tooling</a>
+ </li>
+ </ol>
+ </div>
+</div>
+
+<p>Battery life is the single most important aspect of the mobile user
+experience. A device without power offers no functionality at all.
+For this reason, it is critically important that apps be as respectful of
+battery life as possible.</p>
+
+<p>There are three important things to keep in mind in keeping your app
+power-thrifty:</p>
+<ul>
+<li>Make your apps <em>Lazy First</em>.</li>
+<li>Take advantage of platform features that can help manage your app's battery
+consumption.</li>
+<li>Use tools that can help you identify battery-draining culprits.</li>
+</ul>
+
+<h2 id="lazy">Lazy First</h2>
+
+<p>Making your app Lazy First means looking for ways to reduce and optimize
+operations that are particularly battery-intensive. The core questions
+underpinning Lazy First design are:
+
+<ul>
+
+ <li><strong>Reduce:</strong> Are there redundant operations your app can cut
+out? For example, can it cache downloaded data instead of repeatedly waking
+ up the radio to re-download the data?</li>
+
+ <li><strong>Defer:</strong> Does an app need to perform an action right
+ away? For example,
+ can it wait until the device is charging before it backs data up to the
+ cloud?</li>
+
+ <li><strong>Coalesce:</strong> Can work be batched, instead of putting the
+ device
+ into an active state many times? For example, is it really necessary for
+ several dozen apps to each turn on the radio at separate times to send
+ their messages? Can the messages instead be transmitted during a
+ single awakening of the radio?</li>
+</ul>
+
+<p>
+You should ask these questions when it comes to using the CPU,
+the radio, and the screen. Lazy First design is often a good way
+to tame these battery killers.
+</p>
+
+<p>
+To help you achieve these and other efficiencies, the Android platform
+provides a number of features to help maximize battery life.
+</p>
+
+<h2 id="features">Platform Features</h2>
+
+<p>
+Broadly speaking, the Android platform provides two categories of help
+for you to optimize your app's battery use. First, it provides several
+APIs that you can implement in your app. You can learn more about these APIs in
+<a href="/topic/performance/scheduling.html">Intelligent Job Scheduling</a>
+and <a href="/performance/power/network/index.html">
+Network Use and Battery Consumption</a>.
+</p>
+
+<p>
+There are also internal mechanisms in the platform to help conserve
+battery life. While they are not APIs that you implement programmatically,
+you should still be aware of them so that your app can leverage them
+successfully. For more information, see
+<a href="/training/monitoring-device-state/doze-standby.html">Doze and
+App Standby</a>.</p>
+
+<p>
+You can get even more benefit out of these features by using the tools
+available for the platform to discover the parts of your app that consume
+the most power. Finding what to target is a big step toward
+successful optimization.
+</p>
+
+<h2 id ="toolery">Tooling</h2>
+
+<p>There are tools for Android, including
+<a href="/studio/profile/dev-options-rendering.html">Profile GPU Rendering</a>
+and <a class="external-link"
+href="https://github.com/google/battery-historian">Battery Historian</a>
+to help you identify areas that you can optimize for better battery life.
+Take advantage of these tools to target areas where you can apply the
+principles of Lazy First.
+</p>
+
+<section class="dac-section dac-small" id="latest-games"><div class="wrap">
+ <h2 class="norule" style="margin:0 0">More resources</h2>
+ <div class="resource-widget resource-flow-layout col-16"
+ data-query="collection:develop/performance/landing"
+ data-sortOrder="random"
+ data-cardSizes="6x6"
+ data-maxResults="24"
+ data-items-per-page="24"
+ data-initial-results="3"></div>
+ </div>
+</section>
diff --git a/docs/html/topic/performance/rendering/index.jd b/docs/html/topic/performance/rendering/index.jd
new file mode 100644
index 0000000..1b16df0
--- /dev/null
+++ b/docs/html/topic/performance/rendering/index.jd
@@ -0,0 +1,60 @@
+page.title=Rendering
+page.article=true
+
+page.tags=battery
+page.metaDescription=Learn how to optimize your app's rendering performance.
+
+@jd:body
+
+
+<iframe width="448" height="252"
+ src="//www.youtube.com/embed/wIy8g8yNhNk?autohide=1&showinfo=0"
+ frameborder="0" allowfullscreen=""
+ style="float: right; margin: 0 0 20px 20px;"></iframe>
+
+<p>
+ A key aspect of your app that influences your users' perception of quality is
+ the smoothness with which it renders images and text to the screen. It is
+ important to avoid jank and sluggish responsiveness when your app is drawing
+ to the screen.
+</p>
+
+<p>
+ This section helps you learn several ways to optimize your app's rendering
+ performance: reducing overdraw, optimizing view hierarchies, and taking
+ advantage of the Profile GPU tool.
+</p>
+
+<h2>Rendering Actions</h2>
+
+<dl>
+ <dt>
+ <strong><a href="overdraw.html">
+ Reducing Overdraw</a></strong>
+ </dt>
+ <dd>
+ Minimize the number of times you app redraws the same pixel in a single
+ frame.
+ </dd>
+
+ <dt>
+ <strong><a href="optimizing-view-hierarchies.html">
+ Performance and View Hierarchies</a></strong>
+ </dt>
+ <dd>
+ Make sure your layout and measurement are executing efficiently, and
+ avoid double taxation.
+ </dd>
+
+
+ <dt>
+ <strong><a href="profile-gpu.html">
+ Analyzing with Profile GPU Rendering</a></strong>
+ </dt>
+ <dd>
+ Take advantage of this on-device tool to identify bottlenecks that
+ may be slowing your app's rendering down.
+ </dd>
+
+
+</dl>
diff --git a/docs/html/topic/performance/optimizing-view-hierarchies.jd b/docs/html/topic/performance/rendering/optimizing-view-hierarchies.jd
similarity index 100%
rename from docs/html/topic/performance/optimizing-view-hierarchies.jd
rename to docs/html/topic/performance/rendering/optimizing-view-hierarchies.jd
diff --git a/docs/html/topic/performance/rendering/overdraw.jd b/docs/html/topic/performance/rendering/overdraw.jd
new file mode 100644
index 0000000..c1feff5
--- /dev/null
+++ b/docs/html/topic/performance/rendering/overdraw.jd
@@ -0,0 +1,197 @@
+page.title=Reducing Overdraw
+page.metaDescription=Improve performance by reducing unnecessary rendering.
+
+meta.tags="performance"
+page.tags="performance"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+ <ol>
+
+ <li>
+ <a href="#understanding">Understanding Overdraw</a>
+ </li>
+ <li>
+ <a href="#finding">Finding Overdraw Problems</a>
+ </li>
+ <li>
+ <a href="#fixing">Fixing Overdraw</a>
+ </li>
+ </ol>
+ </div>
+</div>
+
+<p>
+An app may draw the same pixel more than once within a single frame, an event
+called <em>overdraw</em>. Overdraw is usually unnecessary, and best
+eliminated. It manifests itself as a performance problem by wasting GPU time to
+render pixels that don't contribute to what the user sees on the screen.
+</p>
+
+<p>
+This document explains overdraw: what it is, how to diagnose it, and actions you
+can take to eliminate or mitigate it.
+</p>
+
+<h2 name="understanding">Understanding Overdraw</h2>
+
+<p>
+Overdraw refers to the system's drawing a pixel on the screen multiple times
+in a single frame of rendering. For example, if we have a bunch of stacked UI
+cards, each card hides a portion of the one below it.
+</p>
+
+<p>
+However, the system still needs to draw even the hidden portions of the cards
+in the stack. This is because stacked cards are rendered according to the
+<a class="external-link"
+href="https://en.wikipedia.org/wiki/Painter%27s_algorithm">painter's
+algorithm</a>: that is, in back-to-front order.
+This sequence of rendering allows the system to apply proper alpha blending to
+translucent objects such as shadows.
+</p>
+
+<h2 name="finding">Finding Overdraw Problems</h2>
+
+<p>
+The platform offers several tools to help you determine if overdraw is
+affecting your app's performance. These tools are available right on the device,
+and accessible by turning on <strong>Developer Settings</strong></a> under
+<em>Settings</em>. For more information about device developer settings, see
+<a href="/studio/run/device.html#developer-device-options">Run Apps on a
+Hardware Device</a>.
+</p>
+
+<h3 id="dgot">Debug GPU overdraw tool</h3>
+
+<p>
+The Debug GPU Overdraw tool uses color-coding to show the number of times your
+app draws each pixel on the screen. The higher this count, the
+more likely it is that overdraw affects your app's performance.
+</p>
+
+<p>
+For more information on how to use the tool, refer to the related
+<a href="/studio/profile/dev-options-overdraw.html">walkthrough</a>
+and
+<a href="https://io2015codelabs.appspot.com/codelabs/android-performance-debug-gpu-overdraw#1">
+codelab</a>.
+</p>
+
+<h3 id="pgrt">Profile GPU rendering tool</h3>
+
+<p>
+The Profile GPU Rendering tool displays, as a scrolling histogram, the time
+each stage of the rendering pipeline takes to display a single frame. The
+<em>Process</em> part of each bar, indicated in orange, shows when the system
+is swapping buffers; this metric provides important clues about overdraw.
+</p>
+
+<p>
+On less performant GPUs, available fill-rate (the speed at which the GPU can
+fill the frame buffer) can be quite low. As the number of
+pixels required to draw a frame increases, the GPU may take longer to process
+new commands, and ask the rest of the system to wait until it can catch up.
+The <em>Process</em> bar shows that this spike happens as the GPU gets
+overwhelmed trying to draw pixels as fast as possible. Issues other than
+raw numbers of pixels may also cause this metric to spike. For example,
+if the Debug GPU Overdraw tool shows heavy overdraw and <em>Process</em> spikes,
+there's likely an issue with overdraw.
+</p>
+
+<p class="note"><strong>Note: </strong>The
+<a href="https://developer.android.com/studio/profile/dev-options-rendering.html">
+Profile GPU Rendering</a> tool does not
+work with apps that use the NDK. This is because the system pushes framework
+messages to the background whenever OpenGL takes a full-screen context. In
+such cases, you may find a profiling tool provided by the GPU manufacturer
+helpful.</p>
+
+<h2 name="fixing">Fixing Overdraw</h2>
+
+<p>
+There are several strategies you can pursue to reduce or eliminate overdraw:
+</p>
+
+<ul>
+ <li>Removing unneeded backgrounds in layouts.</li>
+ <li>Flattening the view hierarchy.</li>
+ <li>Reducing transparency.</li>
+</ul>
+
+<p>
+This section provides information about each of these approaches.
+</p>
+
+<h3 id="rubil">Removing unneeded backgrounds in layouts</h3>
+
+<p>
+By default, a layout does not have a background, which means it does not render
+anything directly by itself. When layouts do have backgrounds, however, they may
+contribute to overdraw.
+</p>
+
+<p>
+Removing unnecessary backgrounds is a quick way of improving rendering
+performance. An unnecessary background may never be visible because it's
+completely covered by everything else the app is drawing on top of that
+view. For example, the system may entirely cover up a parent's
+background when it draws child views on top of it.
+</p>
+
+<p>
+To find out why you're overdrawing, walk through the hierarchy in
+the <a href="/studio/profile/hierarchy-viewer.html">Hierarchy Viewer</a> tool.
+As you do so, look out for any backgrounds you can eliminate because
+they are not visible to the user. Cases where many containers share a
+common background color offer another opportunity to eliminate unneeded
+backgrounds: You can set the window background to the main background color
+of your app, and leave all of the containers above it with no background values
+defined.
+</p>
+
+<h3 id="fvh">Flattening view hierarchy</h3>
+
+<p>
+Modern layouts make it easy to stack and layer views to produce beautiful
+design. However, doing so can degrade performance by resulting in overdraw,
+especially in scenarios where each stacked view object is opaque, requiring the
+drawing of both seen and unseen pixels to the screen.
+</p>
+
+<p>
+If you encounter this sort of issue, you may be able to improve performance by
+optimizing your view hierarchy to reduce the number of overlapping UI objects.
+For more information about how to accomplish this, see
+<a href="/topic/performance/optimizing-view-hierarchies.html">Optimizing View
+Hierarchies</a>.
+</p>
+
+<h3 id="rt">Reducing transparency</h3>
+
+<p>
+Rendering of transparent pixels on screen, known as alpha rendering, is a key
+contributor to overdraw. Unlike standard overdraw,
+in which the system completely hides existing drawn pixels by drawing
+opaque pixels on top of them, transparent
+objects require existing pixels to be drawn first, so that the right blending
+equation can occur. Visual effects like transparent animations, fade-outs, and
+drop shadows all involve some sort of transparency, and can therefore contribute
+significantly to overdraw. You can improve overdraw in these situations by
+reducing the number of transparent objects you render. For example, you can get
+gray text by drawing black text in a {@link android.widget.TextView} with a
+translucent alpha value set on it. But you can get the same effect with far
+better performance by simply drawing the text in gray.
+</p>
+
+<p>
+To learn more about performance costs that transparency imposes throughout the
+entire drawing pipeline, watch the video
+<a href="https://www.youtube.com/watch?v=wIy8g8yNhNk&index=46&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">
+Hidden Costs of Transparency</a>.
+</p>
+
diff --git a/docs/html/topic/performance/rendering/profile-gpu.jd b/docs/html/topic/performance/rendering/profile-gpu.jd
new file mode 100644
index 0000000..fc98777
--- /dev/null
+++ b/docs/html/topic/performance/rendering/profile-gpu.jd
@@ -0,0 +1,406 @@
+page.title=Analyzing with Profile GPU Rendering
+page.metaDescription=Use the Profile GPU tool to help you optimize your app's rendering performance.
+
+meta.tags="power"
+page.tags="power"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+ <ol>
+ <li>
+ <a href="#visrep">Visual Representation</a></li>
+ </li>
+
+ <li>
+ <a href="#sam">Stages and Their Meanings</a>
+
+ <ul>
+ <li>
+ <a href="#sv">Input Handling</a>
+ </li>
+ <li>
+ <a href="#asd">Animation</a>
+ </li>
+ <li>
+ <a href="#asd">Measurement/Layout</a>
+ </li>
+ <li>
+ <a href="#asd">Drawing</a>
+ </li>
+ </li>
+ <li>
+ <a href="#asd">Sync/Upload</a>
+ </li>
+ <li>
+ <a href="#asd">Issuing Commands</a>
+ </li>
+ <li>
+ <a href="#asd">Processing/Swapping Buffer</a>
+ </li>
+ <li>
+ <a href="#asd">Miscellaneous</a>
+ </li>
+ </ul>
+ </li>
+ </ol>
+ </div>
+</div>
+
+<p>
+The <a href="/studio/profile/dev-options-rendering.html">
+Profile GPU Rendering</a> tool indicates the relative time that each stage of
+the rendering pipeline takes to render the previous frame. This knowledge
+can help you identify bottlenecks in the pipeline, so that you
+can know what to optimize to improve your app's rendering performance.
+</p>
+
+<p>
+This page briefly explains what happens during each pipeline stage, and
+discusses issues that can cause bottlenecks there. Before reading
+this page, you should be familiar with the information presented in the
+<a href="/studio/profile/dev-options-rendering.html">Profile GPU
+Rendering Walkthrough</a>. In addition, to understand how all of the
+stages fit together, it may be helpful to review
+<a href="https://www.youtube.com/watch?v=we6poP0kw6E&index=64&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">
+how the rendering pipeline works.</a>
+</p>
+
+<h2 id="#visrep">Visual Representation</h2>
+
+<p>
+The Profile GPU Rendering tool displays stages and their relative times in the
+form of a graph: a color-coded histogram. Figure 1 shows an example of
+such a display.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/bars.png">
+ <p class="img-caption">
+<strong>Figure 1.</strong> Profile GPU Rendering Graph
+ </p>
+
+</p>
+
+<p>
+Each segment of each vertical bar displayed in the Profile GPU Rendering
+graph represents a stage of the pipeline and is highlighted using a specific
+color in
+the bar graph. Figure 2 shows a key to the meaning of each displayed color.
+</p>
+
+ <img src="{@docRoot}topic/performance/images/s-profiler-legend.png">
+ <p class="img-caption">
+<strong>Figure 2.</strong> Profile GPU Rendering Graph Legend
+ </p>
+
+<p>
+Once you understand what each color signfiies,
+you can target specific aspects of your
+app to try to optimize its rendering performance.
+</p>
+
+<h2 id="sam">Stages and Their Meanings</a></h2>
+
+<p>
+This section explains what happens during each stage corresponding
+to a color in Figure 2, as well as bottleneck causes to look out for.
+</p>
+
+
+<h3 id="ih">Input Handling</h3>
+
+<p>
+The input handling stage of the pipeline measures how long the app
+spent handling input events. This metric indicates how long the app
+spent executing code called as a result of input event callbacks.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+High values in this area are typically a result of too much work, or
+too-complex work, occurring inside the input-handler event callbacks.
+Since these callbacks always occur on the main thread, solutions to this
+problem focus on optimizing the work directly, or offloading the work to a
+different thread.
+</p>
+
+<p>
+It’s also worth noting that {@link android.support.v7.widget.RecyclerView}
+scrolling can appear in this phase.
+{@link android.support.v7.widget.RecyclerView} scrolls immediately when it
+consumes the touch event. As a result,
+it can inflate or populate new item views. For this reason, it’s important to
+make this operation as fast as possible. Profiling tools like Traceview or
+Systrace can help you investigate further.
+</p>
+
+<h3 id="at">Animation</h3>
+
+<p>
+The Animations phase shows you just how long it took to evaluate all the
+animators that were running in that frame. The most common animators are
+{@link android.animation.ObjectAnimator},
+{@link android.view.ViewPropertyAnimator}, and
+<a href="/training/transitions/overview.html">Transitions</a>.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+High values in this area are typically a result of work that’s executing due
+to some property change of the animation. For example, a fling animation,
+which scrolls your {@link android.widget.ListView} or
+{@link android.support.v7.widget.RecyclerView}, causes large amounts of view
+inflation and population.
+</p>
+
+<h3 id="ml">Measurement/Layout</h3>
+
+<p>
+In order for Android to draw your view items on the screen, it executes
+two specific operations across layouts and views in your view hierarchy.
+</p>
+
+<p>
+First, the system measures the view items. Every view and layout has
+specific data that describes the size of the object on the screen. Some views
+can have a specific size; others have a size that adapts to the size
+of the parent layout container
+</p>
+
+<p>
+Second, the system lays out the view items. Once the system calculates
+the sizes of children views, the system can proceed with layout, sizing
+and positioning the views on the screen.
+</p>
+
+<p>
+The system performs measurement and layout not only for the views to be drawn,
+but also for the parent hierarchies of those views, all the way up to the root
+view.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+If your app spends a lot of time per frame in this area, it is
+usually either because of the sheer volume of views that need to be
+laid out, or problems such as
+<a href="/topic/performance/optimizing-view-hierarchies.html#double">
+double taxation</a> at the wrong spot in your
+hierarchy. In either of these cases, addressing performance involves
+<a href="/topic/performance/optimizing-view-hierarchies.html">improving
+the performance of your view hierarchies</a>.
+</p>
+
+<p>
+Code that you’ve added to
+{@link android.view.View#onLayout(boolean, int, int, int, int)} or
+{@link android.view.View#onMeasure(int, int)}
+can also cause performance
+issues. <a href="/studio/profile/traceview.html">Traceview</a> and
+<a href="/studio/profile/systrace.html">Systrace</a> can help you examine
+the callstacks to identify problems your code may have.
+</p>
+
+<h3 id="draw">Drawing</h3>
+
+<p>
+The draw stage translates a view’s rendering operations, such as drawing
+a background or drawing text, into a sequence of native drawing commands.
+The system captures these commands into a display list.
+</p>
+
+<p>
+The Draw bar records how much time it takes to complete capturing the commands
+into the display list, for all the views that needed to be updated on the screen
+this frame. The measured time applies to any code that you have added to the UI
+objects in your app. Examples of such code may be the
+{@link android.view.View#onDraw(android.graphics.Canvas) onDraw()},
+{@link android.view.View#dispatchDraw(android.graphics.Canvas) dispatchDraw()},
+and the various <code>draw ()methods</code> belonging to the subclasses of the
+{@link android.graphics.drawable.Drawable} class.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+In simplified terms, you can understand this metric as showing how long it took
+to run all of the calls to
+{@link android.view.View#onDraw(android.graphics.Canvas) onDraw()}
+for each invalidated view. This
+measurement includes any time spent dispatching draw commands to children and
+drawables that may be present. For this reason, when you see this bar spike, the
+cause could be that a bunch of views suddenly became invalidated. Invalidation
+makes it necessary to regenerate views' display lists. Alternatively, a
+lengthy time may be the result of a few custom views that have some extremely
+complex logic in their
+{@link android.view.View#onDraw(android.graphics.Canvas) onDraw()} methods.
+</p>
+
+<h3 id="su">Sync/Upload</h3>
+
+<p>
+The Sync & Upload metric represents the time it takes to transfer
+bitmap objects from CPU memory to GPU memory during the current frame.
+</p>
+
+<p>
+As different processors, the CPU and the GPU have different RAM areas
+dedicated to processing. When you draw a bitmap on Android, the system
+transfers the bitmap to GPU memory before the GPU can render it to the
+screen. Then, the GPU caches the bitmap so that the system doesn’t need to
+transfer the data again unless the texture gets evicted from the GPU texture
+cache.
+</p>
+
+<p class="note"><strong>Note:</strong> On Lollipop devices, this stage is
+purple.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+All resources for a frame need to reside in GPU memory before they can be
+used to draw a frame. This means that a high value for this metric could mean
+either a large number of small resource loads or a small number of very large
+resources. A common case is when an app displays a single bitmap that’s
+close to the size of the screen. Another case is when an app displays a
+large number of thumbnails.
+</p>
+
+<p>
+To shrink this bar, you can employ techniques such as:
+</p>
+
+<ul>
+ <li>
+Ensuring your bitmap resolutions are not much larger than the size at which they
+will be displayed. For example, your app should avoid displaying a 1024x1024
+image as a 48x48 image.
+ </li>
+
+ <li>
+Taking advantage of {@link android.graphics.Bitmap#prepareToDraw()}
+to asynchronously pre-upload a bitmap before the next sync phase.
+ </li>
+</ul>
+
+<h3 id="ic">Issuing Commands</h3>
+
+<p>
+The <em>Issue Commands</em> segment represents the time it takes to issue all
+of the commands necessary for drawing display lists to the screen.
+</p>
+
+<p>
+For the system to draw display lists to the screen, it sends the
+necessary commands to the GPU. Typically, it performs this action through the
+<a href="/guide/topics/graphics/opengl.html">OpenGL ES</a> API.
+</p>
+
+<p>
+This process takes some time, as the system performs final transformation
+and clipping for each command before sending the command to the GPU. Additional
+overhead then arises on the GPU side, which computes the final commands. These
+commands include final transformations, and additional clipping.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+The time spent in this stage is a direct measure of the complexity and
+quantity of display lists that the system renders in a given
+frame. For example, having many draw operations, especially in cases where
+there's a small inherent cost to each draw primitive, could inflate this time.
+For example:
+</p>
+
+<pre>
+for (int i = 0; i < 1000; i++)
+canvas.drawPoint()
+</pre>
+
+<p>
+is a lot more expensive to issue than:
+</p>
+
+<pre>
+canvas.drawPoints(mThousandPointArray);
+</pre>
+
+<p>
+There isn’t always a 1:1 correlation between issuing commands and
+actually drawing display lists. Unlike <em>Issue Commands</em>,
+which captures the time it takes to send drawing commands to the GPU,
+the <em>Draw</em> metric represents the time that it took to capture the issued
+commands into the display list.
+</p>
+
+<p>
+This difference arises because the display lists are cached by
+the system wherever possible. As a result, there are situations where a
+scroll, transform, or animation requires the system to re-send a display
+list, but not have to actually rebuild it—recapture the drawing
+commands—from scratch. As a result, you can see a high “Issue
+commands” bar without seeing a high <em>Draw commands</em> bar.
+</p>
+
+<h3 id="psb">Processing/Swapping Buffers</h3>
+
+<p>
+Once Android finishes submitting all its display list to the GPU,
+the system issues one final command to tell the graphics driver that it's
+done with the current frame. At this point, the driver can finally present
+the updated image to the screen.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+It’s important to understand that the GPU executes work in parallel with the
+CPU. The Android system issues draw commands to the GPU, and then moves on to
+the next task. The GPU reads those draw commands from a queue and processes
+them.
+</p>
+
+<p>
+In situations where the CPU issues commands faster than the GPU
+consumes them, the communications queue between the processors can become
+full. When this occurs, the CPU blocks, and waits until there is space in the
+queue to place the next command. This full-queue state arises often during the
+<em>Swap Buffers</em> stage, because at that point, a whole frame’s worth of
+commands have been submitted.
+</p>
+
+</p>
+The key to mitigating this problem is to reduce the complexity of work occurring
+on the GPU, in similar fashion to what you would do for the “Issue Commands”
+phase.
+</p>
+
+
+<h3 id="mt">Miscellaneous</h3>
+
+<p>
+In addition to the time it takes the rendering system to perform its work,
+there’s an additional set of work that occurs on the main thread and has
+nothing to do with rendering. Time that this work consumes is reported as
+<em>misc time</em>. Misc time generally represents work that might be occurring
+on the UI thread between two consecutive frames of rendering.
+</p>
+
+<h4>When this segment is large</h4>
+
+<p>
+If this value is high, it is likely that your app has callbacks, intents, or
+other work that should be happening on another thread. Tools such as
+<a href="/studio/profile/traceview.html">Method
+Tracing</a> or <a href="/studio/profile/systrace.html">Systrace</a> can provide
+visibility into the tasks that are running on
+the main thread. This information can help you target performance improvements.
+</p>
diff --git a/docs/html/training/_book.yaml b/docs/html/training/_book.yaml
index e9635be..47862e2 100644
--- a/docs/html/training/_book.yaml
+++ b/docs/html/training/_book.yaml
@@ -438,16 +438,6 @@
path: /training/efficient-downloads/redundant_redundant.html
- title: Modifying Patterns Based on the Connectivity Type
path: /training/efficient-downloads/connectivity_patterns.html
- - title: Backing up App Data to the Cloud
- path: /training/backup/index.html
- path_attributes:
- - name: description
- value: How to sync and back up app and user data to remote web services in the cloud and how to restore the data back to multiple devices.
- section:
- - title: Configuring Auto Backup
- path: /training/backup/autosyncapi.html
- - title: Using the Backup API
- path: /training/backup/backupapi.html
- title: Resolving Cloud Save Conflicts
path: /training/cloudsave/conflict-res.html
path_attributes:
@@ -1156,7 +1146,7 @@
value: 维护兼容性
- name: zh-tw-lang
value: 維持相容性
- - title: Selecting Colors with the Palette API
+ - title: Selecting Colors with the Palette API
path: /training/material/palette-colors.html
- title: Best Practices for User Input
@@ -1242,15 +1232,9 @@
path: /training/scheduling/wakelock.html
- title: Scheduling Repeating Alarms
path: /training/scheduling/alarms.html
-
- title: Best Practices for Performance
path: /training/best-performance.html
section:
- - title: Managing Your App's Memory
- path: /training/articles/memory.html
- path_attributes:
- - name: description
- value: How to keep your app's memory footprint small in order to improve performance on a variety of mobile devices.
- title: Performance Tips
path: /training/articles/perf-tips.html
path_attributes:
@@ -1282,23 +1266,6 @@
- name: description
value: How to minimize the amount of power your app requires by adapting to current power conditions and performing power-hungry tasks at proper intervals.
section:
- - title: Reducing Network Battery Drain
- path: /training/performance/battery/network/index.html
- section:
- - title: Collecting Network Traffic Data
- path: /training/performance/battery/network/gather-data.html
- - title: Analyzing Network Traffic Data
- path: /training/performance/battery/network/analyze-data.html
- - title: Optimizing User-Initiated Network Use
- path: /training/performance/battery/network/action-user-traffic.html
- - title: Optimizing App-Initiated Network Use
- path: /training/performance/battery/network/action-app-traffic.html
- - title: Optimizing Server-Initiated Network Use
- path: /training/performance/battery/network/action-server-traffic.html
- - title: Optimizing General Network Use
- path: /training/performance/battery/network/action-any-traffic.html
- - title: Optimizing for Doze and App Standby
- path: /training/monitoring-device-state/doze-standby.html
- title: Monitoring the Battery Level and Charging State
path: /training/monitoring-device-state/battery-monitoring.html
path_attributes:
diff --git a/docs/html/training/articles/memory.jd b/docs/html/training/articles/memory.jd
deleted file mode 100644
index de7af58..0000000
--- a/docs/html/training/articles/memory.jd
+++ /dev/null
@@ -1,740 +0,0 @@
-page.title=Managing Your App's Memory
-page.tags=ram,low memory,OutOfMemoryError,onTrimMemory
-page.article=true
-@jd:body
-
-
-<div id="tb-wrapper">
-<div id="tb">
-
-<h2>In this document</h2>
-<ol class="nolist">
- <li><a href="#Android">How Android Manages Memory</a>
- <ol>
- <li><a href="#SharingRAM">Sharing Memory</a></li>
- <li><a href="#AllocatingRAM">Allocating and Reclaiming App Memory</a></li>
- <li><a href="#RestrictingMemory">Restricting App Memory</a></li>
- <li><a href="#SwitchingApps">Switching Apps</a></li>
- </ol>
- </li>
- <li><a href="#YourApp">How Your App Should Manage Memory</a>
- <ol>
- <li><a href="#Services">Use services sparingly</a></li>
- <li><a href="#ReleaseMemoryAsUiGone">Release memory when your user interface becomes hidden</a></li>
- <li><a href="#ReleaseMemoryAsTight">Release memory as memory becomes tight</a></li>
- <li><a href="#CheckHowMuchMemory">Check how much memory you should use</a></li>
- <li><a href="#Bitmaps">Avoid wasting memory with bitmaps</a></li>
- <li><a href="#DataContainers">Use optimized data containers</a></li>
- <li><a href="#Overhead">Be aware of memory overhead</a></li>
- <li><a href="#Abstractions">Be careful with code abstractions</a></li>
- <li><a href="#NanoProto">Use nano protobufs for serialized data</a></li>
- <li><a href="#DependencyInjection">Avoid dependency injection frameworks</a></li>
- <li><a href="#ExternalLibs">Be careful about using external libraries</a></li>
- <li><a href="#OverallPerf">Optimize overall performance</a></li>
- <li><a href="#Proguard">Use ProGuard to strip out any unneeded code</a></li>
- <li><a href="#Zipalign">Use zipalign on your final APK</a></li>
- <li><a href="#AnalyzeRam">Analyze your RAM usage</a></li>
- <li><a href="#MultipleProcesses">Use multiple processes</a></li>
- </ol>
- </li>
-</ol>
-<h2>See Also</h2>
-<ul>
- <li><a href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>
- </li>
-</ul>
-
-</div>
-</div>
-
-
-<p>Random-access memory (RAM) is a valuable resource in any software development environment, but
-it's even more valuable on a mobile operating system where physical memory is often constrained.
-Although Android's Dalvik virtual machine performs routine garbage collection, this doesn't allow
-you to ignore when and where your app allocates and releases memory.</p>
-
-<p>In order for the garbage collector to reclaim memory from your app, you need to avoid
-introducing memory leaks (usually caused by holding onto object references in global members) and
-release any {@link java.lang.ref.Reference} objects at the appropriate time (as defined by
-lifecycle callbacks discussed further below). For most apps, the Dalvik garbage collector takes
-care of the rest: the system reclaims your memory allocations when the corresponding objects leave
-the scope of your app's active threads.</p>
-
-<p>This document explains how Android manages app processes and memory allocation, and how you can
-proactively reduce memory usage while developing for Android. For more information about general
-practices to clean up your resources when programming in Java, refer to other books or online
-documentation about managing resource references. If you’re looking for information about how to
-analyze your app’s memory once you’ve already built it, read <a
-href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p>
-
-
-
-
-<h2 id="Android">How Android Manages Memory</h2>
-
-<p>Android does not offer swap space for memory, but it does use <a href=
-"http://en.wikipedia.org/wiki/Paging" class="external-link">paging</a> and <a href=
-"http://en.wikipedia.org/wiki/Memory-mapped_files" class="external-link">memory-mapping</a>
-(mmapping) to manage memory. This means that any memory you modify—whether by allocating
-new objects or touching mmapped pages—remains resident in RAM and cannot be paged out.
-So the only way to completely release memory from your app is to release object references you may
-be holding, making the memory available to the garbage collector. That is with one exception:
-any files mmapped in without modification, such as code, can be paged out of RAM if the system
-wants to use that memory elsewhere.</p>
-
-
-<h3 id="SharingRAM">Sharing Memory</h3>
-
-<p>In order to fit everything it needs in RAM, Android tries to share RAM pages across processes. It
-can do so in the following ways:</p>
-<ul>
-<li>Each app process is forked from an existing process called Zygote.
-The Zygote process starts when the system boots and loads common framework code and resources
-(such as activity themes). To start a new app process, the system forks the Zygote process then
-loads and runs the app's code in the new process. This allows most of the RAM pages allocated for
-framework code and resources to be shared across all app processes.</li>
-
-<li>Most static data is mmapped into a process. This not only allows that same data to be shared
-between processes but also allows it to be paged out when needed. Example static data include:
-Dalvik code (by placing it in a pre-linked {@code .odex} file for direct mmapping), app resources
-(by designing the resource table to be a structure that can be mmapped and by aligning the zip
-entries of the APK), and traditional project elements like native code in {@code .so} files.</li>
-
-<li>In many places, Android shares the same dynamic RAM across processes using explicitly allocated
-shared memory regions (either with ashmem or gralloc). For example, window surfaces use shared
-memory between the app and screen compositor, and cursor buffers use shared memory between the
-content provider and client.</li>
-</ul>
-
-<p>Due to the extensive use of shared memory, determining how much memory your app is using requires
-care. Techniques to properly determine your app's memory use are discussed in <a
-href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p>
-
-
-<h3 id="AllocatingRAM">Allocating and Reclaiming App Memory</h3>
-
-<p>Here are some facts about how Android allocates then reclaims memory from your app:</p>
-
-<ul>
-<li>The Dalvik heap for each process is constrained to a single virtual memory range. This defines
-the logical heap size, which can grow as it needs to (but only up to a limit that the system defines
-for each app).</li>
-
-<li>The logical size of the heap is not the same as the amount of physical memory used by the heap.
-When inspecting your app's heap, Android computes a value called the Proportional Set Size (PSS),
-which accounts for both dirty and clean pages that are shared with other processes—but only in an
-amount that's proportional to how many apps share that RAM. This (PSS) total is what the system
-considers to be your physical memory footprint. For more information about PSS, see the <a
-href="{@docRoot}tools/debugging/debugging-memory.html#ViewingAllocations">Investigating Your
-RAM Usage</a> guide.</li>
-
-<li>The Dalvik heap does not compact the logical size of the heap, meaning that Android does not
-defragment the heap to close up space. Android can only shrink the logical heap size when there
-is unused space at the end of the heap. But this doesn't mean the physical memory used by the heap
-can't shrink. After garbage collection, Dalvik walks the heap and finds unused pages, then returns
-those pages to the kernel using madvise. So, paired allocations and deallocations of large
-chunks should result in reclaiming all (or nearly all) the physical memory used. However,
-reclaiming memory from small allocations can be much less efficient because the page used
-for a small allocation may still be shared with something else that has not yet been freed.</li>
-</ul>
-
-
-<h3 id="RestrictingMemory">Restricting App Memory</h3>
-
-<p>To maintain a functional multi-tasking environment, Android sets a hard limit on the heap size
-for each app. The exact heap size limit varies between devices based on how much RAM the device
-has available overall. If your app has reached the heap capacity and tries to allocate more
-memory, it will receive an {@link java.lang.OutOfMemoryError}.</p>
-
-<p>In some cases, you might want to query the system to determine exactly how much heap space you
-have available on the current device—for example, to determine how much data is safe to keep in a
-cache. You can query the system for this figure by calling {@link
-android.app.ActivityManager#getMemoryClass()}. This returns an integer indicating the number of
-megabytes available for your app's heap. This is discussed further below, under
-<a href="#CheckHowMuchMemory">Check how much memory you should use</a>.</p>
-
-
-<h3 id="SwitchingApps">Switching Apps</h3>
-
-<p>Instead of using swap space when the user switches between apps, Android keeps processes that
-are not hosting a foreground ("user visible") app component in a least-recently used (LRU) cache.
-For example, when the user first launches an app, a process is created for it, but when the user
-leaves the app, that process does <em>not</em> quit. The system keeps the process cached, so if
-the user later returns to the app, the process is reused for faster app switching.</p>
-
-<p>If your app has a cached process and it retains memory that it currently does not need,
-then your app—even while the user is not using it—is constraining the system's
-overall performance. So, as the system runs low on memory, it may kill processes in the LRU cache
-beginning with the process least recently used, but also giving some consideration toward
-which processes are most memory intensive. To keep your process cached as long as possible, follow
-the advice in the following sections about when to release your references.</p>
-
-<p>More information about how processes are cached while not running in the foreground and how
-Android decides which ones
-can be killed is available in the <a href="{@docRoot}guide/components/processes-and-threads.html"
->Processes and Threads</a> guide.</p>
-
-
-
-
-<h2 id="YourApp">How Your App Should Manage Memory</h2>
-
-<p>You should consider RAM constraints throughout all phases of development, including during app
-design (before you begin development). There are many
-ways you can design and write code that lead to more efficient results, through aggregation of the
-same techniques applied over and over.</p>
-
-<p>You should apply the following techniques while designing and implementing your app to make it
-more memory efficient.</p>
-
-
-<h3 id="Services">Use services sparingly</h3>
-
-<p>If your app needs a <a href="{@docRoot}guide/components/services.html">service</a>
-to perform work in the background, do not keep it running unless
-it's actively performing a job. Also be careful to never leak your service by failing to stop it
-when its work is done.</p>
-
-<p>When you start a service, the system prefers to always keep the process for that service
-running. This makes the process very expensive because the RAM used by the service can’t be used by
-anything else or paged out. This reduces the number of cached processes that the system can keep in
-the LRU cache, making app switching less efficient. It can even lead to thrashing in the system
-when memory is tight and the system can’t maintain enough processes to host all the services
-currently running.</p>
-
-<p>The best way to limit the lifespan of your service is to use an {@link
-android.app.IntentService}, which finishes
-itself as soon as it's done handling the intent that started it. For more information, read
-<a href="{@docRoot}training/run-background-service/index.html">Running in a Background Service</a>
-.</p>
-
-<p>Leaving a service running when it’s not needed is <strong>one of the worst memory-management
-mistakes</strong> an Android app can make. So don’t be greedy by keeping a service for your app
-running. Not only will it increase the risk of your app performing poorly due to RAM constraints,
-but users will discover such misbehaving apps and uninstall them.</p>
-
-
-<h3 id="ReleaseMemoryAsUiGone">Release memory when your user interface becomes hidden</h3>
-
-<p>When the user navigates to a different app and your UI is no longer visible, you should
-release any resources that are used by only your UI. Releasing UI resources at this time can
-significantly increase the system's capacity for cached processes, which has a direct impact on the
-quality of the user experience.</p>
-
-<p>To be notified when the user exits your UI, implement the {@link
-android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback in your {@link
-android.app.Activity} classes. You should use this
-method to listen for the {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN} level,
-which indicates your UI is now hidden from view and you should free resources that only your UI
-uses.</p>
-
-
-<p>Notice that your app receives the {@link android.content.ComponentCallbacks2#onTrimMemory
-onTrimMemory()} callback with {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN}
-only when <em>all the UI components</em> of your app process become hidden from the user.
-This is distinct
-from the {@link android.app.Activity#onStop onStop()} callback, which is called when an {@link
-android.app.Activity} instance becomes hidden, which occurs even when the user moves to
-another activity in your app. So although you should implement {@link android.app.Activity#onStop
-onStop()} to release activity resources such as a network connection or to unregister broadcast
-receivers, you usually should not release your UI resources until you receive {@link
-android.content.ComponentCallbacks2#onTrimMemory onTrimMemory(TRIM_MEMORY_UI_HIDDEN)}. This ensures
-that if the user navigates <em>back</em> from another activity in your app, your UI resources are
-still available to resume the activity quickly.</p>
-
-
-
-<h3 id="ReleaseMemoryAsTight">Release memory as memory becomes tight</h3>
-
-<p>During any stage of your app's lifecycle, the {@link
-android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback also tells you when
-the overall device memory is getting low. You should respond by further releasing resources based
-on the following memory levels delivered by {@link android.content.ComponentCallbacks2#onTrimMemory
-onTrimMemory()}:</p>
-
-<ul>
-<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_MODERATE}
-<p>Your app is running and not considered killable, but the device is running low on memory and the
-system is actively killing processes in the LRU cache.</p>
-</li>
-
-<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_LOW}
-<p>Your app is running and not considered killable, but the device is running much lower on
-memory so you should release unused resources to improve system performance (which directly
-impacts your app's performance).</p>
-</li>
-
-<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_CRITICAL}
-<p>Your app is still running, but the system has already killed most of the processes in the
-LRU cache, so you should release all non-critical resources now. If the system cannot reclaim
-sufficient amounts of RAM, it will clear all of the LRU cache and begin killing processes that
-the system prefers to keep alive, such as those hosting a running service.</p>
-</li>
-</ul>
-
-<p>Also, when your app process is currently cached, you may receive one of the following
-levels from {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()}:</p>
-<ul>
-<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_BACKGROUND}
-<p>The system is running low on memory and your process is near the beginning of the LRU list.
-Although your app process is not at a high risk of being killed, the system may already be killing
-processes in the LRU cache. You should release resources that are easy to recover so your process
-will remain in the list and resume quickly when the user returns to your app.</p>
-</li>
-
-<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_MODERATE}
-<p>The system is running low on memory and your process is near the middle of the LRU list. If the
-system becomes further constrained for memory, there's a chance your process will be killed.</p>
-</li>
-
-<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE}
-<p>The system is running low on memory and your process is one of the first to be killed if the
-system does not recover memory now. You should release everything that's not critical to
-resuming your app state.</p>
-
-</li>
-</ul>
-
-<p>Because the {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback was
-added in API level 14, you can use the {@link android.content.ComponentCallbacks#onLowMemory()}
-callback as a fallback for older versions, which is roughly equivalent to the {@link
-android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE} event.</p>
-
-<p class="note"><strong>Note:</strong> When the system begins killing processes in the LRU cache,
-although it primarily works bottom-up, it does give some consideration to which processes are
-consuming more memory and will thus provide the system more memory gain if killed.
-So the less memory you consume while in the LRU list overall, the better your chances are
-to remain in the list and be able to quickly resume.</p>
-
-
-
-<h3 id="CheckHowMuchMemory">Check how much memory you should use</h3>
-
-<p>As mentioned earlier, each Android-powered device has a different amount of RAM available to the
-system and thus provides a different heap limit for each app. You can call {@link
-android.app.ActivityManager#getMemoryClass()} to get an estimate of your app's available heap in
-megabytes. If your app tries to allocate more memory than is available here, it will receive an
-{@link java.lang.OutOfMemoryError}.</p>
-
-<p>In very special situations, you can request a larger heap size by setting the <a
-href="{@docRoot}guide/topics/manifest/application-element.html#largeHeap">{@code largeHeap}</a>
-attribute to "true" in the manifest <a
-href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
-tag. If you do so, you can call {@link
-android.app.ActivityManager#getLargeMemoryClass()} to get an estimate of the large heap size.</p>
-
-<p>However, the ability to request a large heap is intended only for a small set of apps that can
-justify the need to consume more RAM (such as a large photo editing app). <strong>Never request a
-large heap simply because you've run out of memory</strong> and you need a quick fix—you
-should use it only when you know exactly where all your memory is being allocated and why it must
-be retained. Yet, even when you're confident your app can justify the large heap, you should avoid
-requesting it to whatever extent possible. Using the extra memory will increasingly be to the
-detriment of the overall user experience because garbage collection will take longer and system
-performance may be slower when task switching or performing other common operations.</p>
-
-<p>Additionally, the large heap size is not the same on all devices and, when running on
-devices that have limited RAM, the large heap size may be exactly the same as the regular heap
-size. So even if you do request the large heap size, you should call {@link
-android.app.ActivityManager#getMemoryClass()} to check the regular heap size and strive to always
-stay below that limit.</p>
-
-
-<h3 id="Bitmaps">Avoid wasting memory with bitmaps</h3>
-
-<p>When you load a bitmap, keep it in RAM only at the resolution you need for the current device's
-screen, scaling it down if the original bitmap is a higher resolution. Keep in mind that an
-increase in bitmap resolution results in a corresponding (increase<sup>2</sup>) in memory needed,
-because both the X and Y dimensions increase.</p>
-
-<p class="note"><strong>Note:</strong> On Android 2.3.x (API level 10) and below, bitmap objects
-always appear as the same size in your app heap regardless of the image resolution (the actual
-pixel data is stored separately in native memory). This makes it more difficult to debug the bitmap
-memory allocation because most heap analysis tools do not see the native allocation. However,
-beginning in Android 3.0 (API level 11), the bitmap pixel data is allocated in your app's Dalvik
-heap, improving garbage collection and debuggability. So if your app uses bitmaps and you're having
-trouble discovering why your app is using some memory on an older device, switch to a device
-running Android 3.0 or higher to debug it.</p>
-
-<p>For more tips about working with bitmaps, read <a
-href="{@docRoot}training/displaying-bitmaps/manage-memory.html">Managing Bitmap Memory</a>.</p>
-
-
-<h3 id="DataContainers">Use optimized data containers</h3>
-
-<p>Take advantage of optimized containers in the Android framework, such as {@link
-android.util.SparseArray}, {@link android.util.SparseBooleanArray}, and {@link
-android.support.v4.util.LongSparseArray}. The generic {@link java.util.HashMap}
-implementation can be quite memory
-inefficient because it needs a separate entry object for every mapping. Additionally, the {@link
-android.util.SparseArray} classes are more efficient because they avoid the system's need
-to <acronym title=
-"Automatic conversion from primitive types to object classes (such as int to Integer)"
->autobox</acronym>
-the key and sometimes value (which creates yet another object or two per entry). And don't be
-afraid of dropping down to raw arrays when that makes sense.</p>
-
-
-
-<h3 id="Overhead">Be aware of memory overhead</h3>
-
-<p>Be knowledgeable about the cost and overhead of the language and libraries you are using, and
-keep this information in mind when you design your app, from start to finish. Often, things on the
-surface that look innocuous may in fact have a large amount of overhead. Examples include:</p>
-<ul>
-<li>Enums often require more than twice as much memory as static constants. You should strictly
-avoid using enums on Android.</li>
-
-<li>Every class in Java (including anonymous inner classes) uses about 500 bytes of code.</li>
-
-<li>Every class instance has 12-16 bytes of RAM overhead.</li>
-
-<li>Putting a single entry into a {@link java.util.HashMap} requires the allocation of an
-additional entry object that takes 32 bytes (see the previous section about <a
-href="#DataContainers">optimized data containers</a>).</li>
-</ul>
-
-<p>A few bytes here and there quickly add up—app designs that are class- or object-heavy will suffer
-from this overhead. That can leave you in the difficult position of looking at a heap analysis and
-realizing your problem is a lot of small objects using up your RAM.</p>
-
-
-<h3 id="Abstractions">Be careful with code abstractions</h3>
-
-<p>Often, developers use abstractions simply as a "good programming practice," because abstractions
-can improve code flexibility and maintenance. However, abstractions come at a significant cost:
-generally they require a fair amount more code that needs to be executed, requiring more time and
-more RAM for that code to be mapped into memory. So if your abstractions aren't supplying a
-significant benefit, you should avoid them.</p>
-
-
-<h3 id="NanoProto">Use nano protobufs for serialized data</h3>
-
-<p><a href="https://developers.google.com/protocol-buffers/docs/overview">Protocol
-buffers</a> are a language-neutral, platform-neutral, extensible mechanism designed by Google for
-serializing structured data—think XML, but smaller, faster, and simpler. If you decide to use
-protobufs for your data, you should always use nano protobufs in your client-side code. Regular
-protobufs generate extremely verbose code, which will cause many kinds of problems in your app:
-increased RAM use, significant APK size increase, slower execution, and quickly hitting the DEX
-symbol limit.</p>
-
-<p>For more information, see the "Nano version" section in the <a
-href="https://android.googlesource.com/platform/external/protobuf/+/master/java/README.txt"
-class="external-link">protobuf readme</a>.</p>
-
-
-
-<h3 id="DependencyInjection">Avoid dependency injection frameworks</h3>
-
-<p>Using a dependency injection framework such as <a
-href="https://code.google.com/p/google-guice/" class="external-link">Guice</a> or
-<a href="https://github.com/roboguice/roboguice" class="external-link">RoboGuice</a> may be
-attractive because they can simplify the code you write and provide an adaptive environment
-that's useful for testing and other configuration changes. However, these frameworks tend to perform
-a lot of process initialization by scanning your code for annotations, which can require significant
-amounts of your code to be mapped into RAM even though you don't need it. These mapped pages are
-allocated into clean memory so Android can drop them, but that won't happen until the pages have
-been left in memory for a long period of time.</p>
-
-
-<h3 id="ExternalLibs">Be careful about using external libraries</h3>
-
-<p>External library code is often not written for mobile environments and can be inefficient when used
-for work on a mobile client. At the very least, when you decide to use an external library, you
-should assume you are taking on a significant porting and maintenance burden to optimize the
-library for mobile. Plan for that work up-front and analyze the library in terms of code size and
-RAM footprint before deciding to use it at all.</p>
-
-<p>Even libraries supposedly designed for use on Android are potentially dangerous because each
-library may do things differently. For example, one library may use nano protobufs while another
-uses micro protobufs. Now you have two different protobuf implementations in your app. This can and
-will also happen with different implementations of logging, analytics, image loading frameworks,
-caching, and all kinds of other things you don't expect. <a
-href="{@docRoot}tools/help/proguard.html">ProGuard</a> won't save you here because these
-will all be lower-level dependencies that are required by the features for which you want the
-library. This becomes especially problematic when you use an {@link android.app.Activity}
-subclass from a library (which
-will tend to have wide swaths of dependencies), when libraries use reflection (which is common and
-means you need to spend a lot of time manually tweaking ProGuard to get it to work), and so on.</p>
-
-<p>Also be careful not to fall into the trap of using a shared library for one or two features out of
-dozens of other things it does; you don't want to pull in a large amount of code and overhead that
-you don't even use. At the end of the day, if there isn't an existing implementation that is a
-strong match for what you need to do, it may be best if you create your own implementation.</p>
-
-
-<h3 id="OverallPerf">Optimize overall performance</h3>
-
-<p>A variety of information about optimizing your app's overall performance is available
-in other documents listed in <a href="{@docRoot}training/best-performance.html">Best Practices
-for Performance</a>. Many of these documents include optimizations tips for CPU performance, but
-many of these tips also help optimize your app's memory use, such as by reducing the number of
-layout objects required by your UI.</p>
-
-<p>You should also read about <a href="{@docRoot}tools/debugging/debugging-ui.html">optimizing
-your UI</a> with the layout debugging tools and take advantage of
-the optimization suggestions provided by the <a
-href="{@docRoot}tools/debugging/improving-w-lint.html">lint tool</a>.</p>
-
-
-<h3 id="Proguard">Use ProGuard to strip out any unneeded code</h3>
-
-<p>The <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> tool shrinks,
-optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and
-methods with semantically obscure names. Using ProGuard can make your code more compact, requiring
-fewer RAM pages to be mapped.</p>
-
-
-<h3 id="Zipalign">Use zipalign on your final APK</h3>
-
-<p>If you do any post-processing of an APK generated by a build system (including signing it
-with your final production certificate), then you must run <a
-href="{@docRoot}tools/help/zipalign.html">zipalign</a> on it to have it re-aligned.
-Failing to do so can cause your app to require significantly more RAM, because things like
-resources can no longer be mmapped from the APK.</p>
-
-<p class="note"><strong>Note:</strong> Google Play Store does not accept APK files that
-are not zipaligned.</p>
-
-
-<h3 id="AnalyzeRam">Analyze your RAM usage</h3>
-
-<p>Once you achieve a relatively stable build, begin analyzing how much RAM your app is using
-throughout all stages of its lifecycle. For information about how to analyze your app, read <a
-href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p>
-
-
-
-
-<h3 id="MultipleProcesses">Use multiple processes</h3>
-
-<p>If it's appropriate for your app, an advanced technique that may help you manage your app's
-memory is dividing components of your app into multiple processes. This technique must always be
-used carefully and <strong>most apps should not run multiple processes</strong>, as it can easily
-increase—rather than decrease—your RAM footprint if done incorrectly. It is primarily
-useful to apps that may run significant work in the background as well as the foreground and can
-manage those operations separately.</p>
-
-
-<p>An example of when multiple processes may be appropriate is when building a music player that
-plays music from a service for long period of time. If
-the entire app runs in one process, then many of the allocations performed for its activity UI must
-be kept around as long as it is playing music, even if the user is currently in another app and the
-service is controlling the playback. An app like this may be split into two process: one for its
-UI, and the other for the work that continues running in the background service.</p>
-
-<p>You can specify a separate process for each app component by declaring the <a href=
-"{@docRoot}guide/topics/manifest/service-element.html#proc">{@code android:process}</a> attribute
-for each component in the manifest file. For example, you can specify that your service should run
-in a process separate from your app's main process by declaring a new process named "background"
-(but you can name the process anything you like):</p>
-
-<pre>
-<service android:name=".PlaybackService"
- android:process=":background" />
-</pre>
-
-<p>Your process name should begin with a colon (':') to ensure that the process remains private to
-your app.</p>
-
-<p>Before you decide to create a new process, you need to understand the memory implications.
-To illustrate the consequences of each process, consider that an empty process doing basically
-nothing has an extra memory footprint of about 1.4MB, as shown by the memory information
-dump below.</p>
-
-<pre class="no-pretty-print">
-adb shell dumpsys meminfo com.example.android.apis:empty
-
-** MEMINFO in pid 10172 [com.example.android.apis:empty] **
- Pss Pss Shared Private Shared Private Heap Heap Heap
- Total Clean Dirty Dirty Clean Clean Size Alloc Free
- ------ ------ ------ ------ ------ ------ ------ ------ ------
- Native Heap 0 0 0 0 0 0 1864 1800 63
- Dalvik Heap 764 0 5228 316 0 0 5584 5499 85
- Dalvik Other 619 0 3784 448 0 0
- Stack 28 0 8 28 0 0
- Other dev 4 0 12 0 0 4
- .so mmap 287 0 2840 212 972 0
- .apk mmap 54 0 0 0 136 0
- .dex mmap 250 148 0 0 3704 148
- Other mmap 8 0 8 8 20 0
- Unknown 403 0 600 380 0 0
- TOTAL 2417 148 12480 1392 4832 152 7448 7299 148
-</pre>
-
-<p class="note"><strong>Note:</strong> More information about how to read this output is provided
-in <a href="{@docRoot}tools/debugging/debugging-memory.html#ViewingAllocations">Investigating
-Your RAM Usage</a>. The key data here is the <em>Private Dirty</em> and <em>Private
-Clean</em> memory, which shows that this process is using almost 1.4MB of non-pageable RAM
-(distributed across the Dalvik heap, native allocations, book-keeping, and library-loading),
-and another 150K of RAM for code that has been mapped in to execute.</p>
-
-<p>This memory footprint for an empty process is fairly significant and it can quickly
-grow as you start doing work in that process. For
-example, here is the memory use of a process that is created only to show an activity with some
-text in it:</p>
-
-<pre class="no-pretty-print">
-** MEMINFO in pid 10226 [com.example.android.helloactivity] **
- Pss Pss Shared Private Shared Private Heap Heap Heap
- Total Clean Dirty Dirty Clean Clean Size Alloc Free
- ------ ------ ------ ------ ------ ------ ------ ------ ------
- Native Heap 0 0 0 0 0 0 3000 2951 48
- Dalvik Heap 1074 0 4928 776 0 0 5744 5658 86
- Dalvik Other 802 0 3612 664 0 0
- Stack 28 0 8 28 0 0
- Ashmem 6 0 16 0 0 0
- Other dev 108 0 24 104 0 4
- .so mmap 2166 0 2824 1828 3756 0
- .apk mmap 48 0 0 0 632 0
- .ttf mmap 3 0 0 0 24 0
- .dex mmap 292 4 0 0 5672 4
- Other mmap 10 0 8 8 68 0
- Unknown 632 0 412 624 0 0
- TOTAL 5169 4 11832 4032 10152 8 8744 8609 134
-</pre>
-
-<p>The process has now almost tripled in size, to 4MB, simply by showing some text in the UI. This
-leads to an important conclusion: If you are going to split your app into multiple processes, only
-one process should be responsible for UI. Other processes should avoid any UI, as this will quickly
-increase the RAM required by the process (especially once you start loading bitmap assets and other
-resources). It may then be hard or impossible to reduce the memory usage once the UI is drawn.</p>
-
-<p>Additionally, when running more than one process, it's more important than ever that you keep your
-code as lean as possible, because any unnecessary RAM overhead for common implementations are now
-replicated in each process. For example, if you are using enums (though <a
-href="#Overhead">you should not use enums</a>), all of
-the RAM needed to create and initialize those constants is duplicated in each process, and any
-abstractions you have with adapters and temporaries or other overhead will likewise be replicated.</p>
-
-<p>Another concern with multiple processes is the dependencies that exist between them. For example,
-if your app has a content provider that you have running in the default process which also hosts
-your UI, then code in a background process that uses that content provider will also require that
-your UI process remain in RAM. If your goal is to have a background process that can run
-independently of a heavy-weight UI process, it can't have dependencies on content providers or
-services that execute in the UI process.</p>
-
-
-
-
-
-
-
-
-
-
-<!-- THE FOLLOWING IS OVERWHELMING AND NOT NECESSARY FOR MOST APPS, LEAVING OUT FOR NOW
-
-
-<p>You can examine the dependencies between your processes with the command:</p>
-
-<pre class="no-pretty-print">
-adb shell dumpsys activity
-</pre>
-
-<p>This dumps various information about the Activity Manager's state, ending with a list of all
-processes in their memory management order, including the reason each process is at its given
-level. For example, below is a dump with the Music app in the foreground.</p>
-
-<pre class="no-pretty-print">
-ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
- Process LRU list (sorted by oom_adj):
- PERS # 4: adj=sys /F trm= 0 20674:system/1000 (fixed)
- PERS #39: adj=pers /F trm= 0 20964:com.android.nfc/1027 (fixed)
- PERS # 2: adj=pers /F trm= 0 20959:com.android.phone/1001 (fixed)
- PERS # 1: adj=pers /F trm= 0 20779:com.android.systemui/u0a10057 (fixed)
- Proc #11: adj=fore /FA trm= 0 8663:com.google.android.music:ui/u0a10043 (top-activity)
- Proc #10: adj=fore /F trm= 0 30881:com.google.android.music:main/u0a10043 (provider)
- com.google.android.music/.store.MusicContentProvider<=Proc{8663:com.google.android.music:ui/u0a10043}
- Proc # 6: adj=fore /F trm= 0 21014:com.google.process.gapps/u0a10023 (provider)
- com.google.android.gsf/.settings.GoogleSettingsProvider<=Proc{20935:com.google.process.location/u0a10023}
- Proc #38: adj=vis /F trm= 0 21028:com.android.nfc:handover/1027 (service)
- com.android.nfc/.handover.HandoverService<=Proc{20964:com.android.nfc/1027}
- Proc # 7: adj=vis /B trm= 0 20935:com.google.process.location/u0a10023 (service)
- com.google.android.location/.GeocodeService<=Proc{20674:system/1000}
- Proc # 3: adj=vis /F trm= 0 21225:com.android.bluetooth/1002 (service)
- com.android.bluetooth/.hfp.HeadsetService<=Proc{20674:system/1000}
- Proc # 0: adj=vis /F trm= 0 20908:com.google.android.inputmethod.latin/u0a10035 (service)
- com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME<=Proc{20674:system/1000}
- Proc #34: adj=svc /B trm= 0 16765:com.google.android.apps.currents/u0a10012 (started-services)
- Proc #14: adj=svc /B trm= 0 21148:com.google.android.gms/u0a10023 (started-services)
- Proc #12: adj=home /B trm= 0 20989:com.android.launcher/u0a10036 (home)
- Proc #37: adj=svcb /B trm= 0 15194:com.google.android.apps.googlevoice/u0a10089 (started-services)
- Proc #17: adj=svcb /B trm= 0 24537:android.process.media/u0a10016 (started-services)
- Proc #35: adj=bak /B trm= 0 16087:com.android.defcontainer/u0a10013 (service)
- com.android.defcontainer/.DefaultContainerService<=Proc{16050:com.android.settings/1000}
- Proc #16: adj=bak /B trm= 0 7334:com.google.android.gm/u0a10022 (bg-act)
- Proc #15: adj=bak /B trm= 0 22499:com.google.android.googlequicksearchbox/u0a10060 (bg-act)
- Proc # 9: adj=bak /B trm= 0 20856:com.google.android.gsf.login/u0a10023 (bg-empty)
- Proc #26: adj=bak+1/B trm= 0 9923:com.android.mms/u0a10042 (bg-act)
- Proc #23: adj=bak+1/B trm= 0 16721:com.android.chrome/u0a10010 (bg-act)
- Proc #22: adj=bak+1/B trm= 0 17596:com.android.chrome:sandboxed_process0/u0a10010i33 (service)
- com.android.chrome/org.chromium.content.app.SandboxedProcessService0<=Proc{16721:com.android.chrome/u0a10010}
- Proc #19: adj=bak+1/B trm= 0 17442:com.google.android.youtube/u0a10067 (bg-services)
- Proc #18: adj=bak+2/B trm= 0 16740:com.google.android.apps.plus/u0a10052 (bg-empty)
- Proc #13: adj=bak+2/B trm= 0 7707:com.android.musicfx/u0a10044 (bg-empty)
- Proc #36: adj=bak+3/B trm= 0 16050:com.android.settings/1000 (bg-act)
- Proc #33: adj=bak+3/B trm= 0 16863:com.android.dialer/u0a10015 (bg-act)
-</pre>
-
-
-<p class="note"><strong>Note:</strong> The exact details of what is shown here will vary across
-platform versions as process management policies are tweaked and improved.</p>
-
-
-<p>Details on the highlighted sections are:</p>
-
-<ol>
-<li>Foreground app: This is the current app running in the foreground -- it is in the "fore" memory
-class because it is the top activity on the activity stack.</li>
-
-<li>Persistent processes: These are processes that are part of the core system that must always be
-running.</li>
-
-<li>Dependent process: This shows how the Music app is using two processes. Its UI process has a
-dependency on the "main" process (through a content provider). So while the UI process is in use,
-the main process must also be kept around. This means the app's memory footprint is actually the
-sum of both processes. You will have this kind of connection on a content provider any time you
-have active calls into it or have unclosed cursors or file streams that came from it.</li>
-
-<li>Visible processes: These are processes that count in some way as "visible" to the user. This
-generally means that it is either something the user can literally see (such as a process hosting a
-paused but visible activity that is behind a non-full-screen dialog) or is something the user might
-notice if the process disappeared (such as a foreground service playing music). You should be
-certain that any process you have running at the "visible" level is indeed critical to the user,
-because they are very expensive to the overall RAM load.</li>
-
-<li>Service processes: These are processes running long-term jobs in a service. This level of the
-list is the start of less-critical processes, which the system has some freedom to kill if RAM is
-needed elsewhere. These services are still quite expensive because they can be killed only
-temporarily and the system tries to keep them running whenever possible.</li>
-
-<li>Home process: A special slot for the process that hosts the current Home activity, to try to
-prevent it from being killed as much as possible. Killing this process is much more damaging to the
-user experience than killing other cached processes, because so much user interaction goes through
-home.</li>
-
-<li>Secondary service processes: These are services that have been running for a relatively long time
-and so should be killed more aggressively when RAM is needed elsewhere.</li>
-
-<li>Cached processes: These are cached processes held in the LRU cache, which allow for fast app
-switching and component launching. These processes are not required and the system will kill them
-as needed to reclaim memory. You will often see a process hosting a running service here—this is
-part of a platform policy of allowing very long-running services to drop down into the LRU list and
-eventually be killed. If the service should continue running (as defined by the {@link
-android.app.Service#onStartCommand onStartCommand()} return value, such as {@link
-android.app.Service#START_STICKY}), the the system eventually restarts it. This avoids issues with
-such services having memory leaks that over time reduce the number of regular cached processes that
-can be kept.</li>
-
-</ol>
-
-<p>This numbered list of processes is essentially the LRU list of processes that the framework
-provides to the kernel to help it determine which processes it should kill as it needs more RAM.
-The kernel's out of memory killer will generally begin from the bottom of this list, killing the
-last process and working its way up. It may not do it in exactly this order, as it can also take
-into consideration other factors such as the relative RAM footprint of processes to some degree.</p>
-
-<p>There are many other options you can use with the activity command to analyze further details of
-your app's state—use <code>adb shell dumpsys activity -h</code> for help on its use.</p>
-
--->
diff --git a/docs/html/training/articles/perf-tips.jd b/docs/html/training/articles/perf-tips.jd
index 82de69a..30cab14 100644
--- a/docs/html/training/articles/perf-tips.jd
+++ b/docs/html/training/articles/perf-tips.jd
@@ -28,7 +28,8 @@
performance effects. Choosing the right algorithms and data structures should always be your
priority, but is outside the scope of this document. You should use the tips in this document
as general coding practices that you can incorporate into your habits for general code
-efficiency.</p>
+efficiency.
+</p>
<p>There are two basic rules for writing efficient code:</p>
<ul>
@@ -49,8 +50,7 @@
without.</p>
<p>To ensure your app performs well across a wide variety of devices, ensure
-your code is efficient at all levels and agressively optimize your performance.</p>
-
+your code is efficient at all levels and aggressively optimize your performance.</p>
<h2 id="ObjectCreation">Avoid Creating Unnecessary Objects</h2>
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/auto/audio/index.jd b/docs/html/training/auto/audio/index.jd
index 3a1b1e88..6588367 100644
--- a/docs/html/training/auto/audio/index.jd
+++ b/docs/html/training/auto/audio/index.jd
@@ -596,7 +596,7 @@
</a>
<h2 id="support_voice">Support Voice Actions</h2>
-<p>To reduce driver distractions, you can add voice actions in your audio playback app. With voice
+<p>To reduce driver distractions, you must add voice actions in your audio playback app. With voice
action support, users can launch your app and play audio by providing voice input on Auto screens.
If your audio playback app is already active and the user says
<i>“Play a song”</i>, the system starts playing music without requiring the user to look at or touch
diff --git a/docs/html/training/backup/autosyncapi.jd b/docs/html/training/backup/autosyncapi.jd
deleted file mode 100644
index e0df7bb..0000000
--- a/docs/html/training/backup/autosyncapi.jd
+++ /dev/null
@@ -1,370 +0,0 @@
-page.title=Configuring Auto Backup for Apps
-page.tags=backup, marshmallow, androidm
-page.keywords=backup, autobackup
-page.image=images/cards/card-auto-backup_2x.png
-
-@jd:body
-
-<div id="tb-wrapper">
-<div id="tb">
-<h2>This lesson teaches you to</h2>
-<ol>
- <li><a href="#configuring">Configure Data Backup</a></li>
- <li><a href="#previous-androids">Support Lower Versions of Android</a></li>
- <li><a href="#testing">Test Backup Configuration</a></li>
- <li><a href="#issues">Handle Google Cloud Messaging</a></li>
-</ol>
- <h2>You should also read</h2>
- <ul>
- <li><a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a></li>
- <li><a href="{@docRoot}training/backup/backupapi.html">Using the Backup API</a>
- </li>
- </ul>
-
-</div>
-</div>
-
-<p>
- Users frequently invest time and effort to configure apps just the way they like them. Switching
- to a new device can cancel out all that careful configuration. For apps whose <a href=
- "{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">target SDK version</a>
- is Android 6.0 (API level 23) and higher, devices running Android 6.0 and higher automatically
- back up app data to the cloud. The system performs this automatic backup
- for nearly all app data by default, and does so without your having to write any additional app
- code.
-</p>
-
-<p class="note">
-<strong>Note:</strong> To protect user privacy, the device user must have opted in to Google
-services for Auto Backup to work. The Google services opt-in dialog appears when the user goes
-through the Setup Wizard or configures the first Google account on the device.
-</p>
-
-<p>
- When a user installs your app on
- a new device, or reinstalls your app on one (for example, after a factory reset), the system
- automatically restores the app data from the cloud. This lesson provides information about how to
- configure the Auto Backup for Apps feature, explaining its default behavior and how to
- exclude data that you don't want the system to back up.
-</p>
-
-<p>
- The automatic backup feature preserves the data your app creates on a user device by uploading it
- to the user’s Google Drive account and encrypting it. There is no charge to you or the user for
- data storage, and the saved data does not count towards the user's personal Google Drive quota.
- Each app can store up to 25MB. Once its backed-up data reaches 25MB, the app no longer sends
- data to the cloud. If the system performs a data restore, it uses the last data snapshot that
- the app had sent to the cloud.
-</p>
-
-<p>Automatic backups occur when the following conditions are met:</p>
- <ul>
- <li>The device is idle.</li>
- <li>The device is charging.</li>
- <li>The device is connected to a Wi-Fi network.</li>
- <li>At least 24 hours have elapsed since the last backup.</li>
- </ul>
-</p>
-
-<h2 id="configuring">Configure Data Backup</h2>
-
-<p>
- On devices running Android 6.0 (API level 23) or higher, the default system behavior is to back up
- almost all data that an app creates. The exception is <a href="#auto-exclude">
- automatically excluded data files</a>. This section explains how you can use settings in
- your app <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> to further
- limit and configure what data the system backs up.
-</p>
-
-<h3 id="include-exclude">Including or excluding data</h3>
-
-<p>
- Depending on what data your app needs and how you save it, you may need to set specific
- rules for including or excluding certain files or directories. Auto Backup for Apps
- lets you set these backup rules through the app manifest, in which you specify a backup scheme
- configuration XML file. For example:
-</p>
-
-<pre>
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.my.appexample">
- <uses-sdk android:minSdkVersion="23"/>
- <uses-sdk android:targetSdkVersion="23"/>
- <application ...
-<strong> android:fullBackupContent="@xml/mybackupscheme"></strong>
- </app>
- ...
-</manifest>
-</pre>
-
-<p>
- In this example, the <code>android:fullBackupContent</code> attribute specifies an XML file
- called {@code mybackupscheme.xml}, which resides in the <code>res/xml/</code> directory of your
- app development project. This configuration file contains rules controlling which files are backed
- up. The following example code shows a configuration file that excludes a specific file,
- {@code device_info.db}:
-</p>
-
-<pre>
-<?xml version="1.0" encoding="utf-8"?>
-<full-backup-content>
- <exclude domain="database" path="device_info.db"/>
-</full-backup-content>
-</pre>
-
-<h3 id="auto-exclude">Automatically excluded data files</h3>
-
-<p>
- Most apps do not need to, and in fact should not, back up all data. For example, the system
- should not back up temporary files and caches. For this reason, the automatic backup
- service excludes certain data files by default:
-</p>
-
-<ul>
- <li>Files in the directories to which the
- {@link android.content.Context#getCacheDir getCacheDir()} and
- {@link android.content.Context#getCodeCacheDir getCodeCacheDir()} methods refer.
- </li>
-
- <li>Files located on external storage, unless they reside in the directory to which the
- {@link android.content.Context#getExternalFilesDir getExternalFilesDir()} method refers.
- </li>
-
- <li>Files located in the directory to which the
- {@link android.content.Context#getNoBackupFilesDir getNoBackupFilesDir()} method refers.
- </li>
-</ul>
-<h3>Backup Configuration Syntax</h3>
-
-<p>
- The backup service configuration allows you to specify what files to include or exclude from
- backup. The syntax for the data backup configuration XML file is as follows:
-</p>
-
-<pre>
-<full-backup-content>
- <include domain=["file" | "database" | "sharedpref" | "external" | "root"]
- path="string" />
- <exclude domain=["file" | "database" | "sharedpref" | "external" | "root"]
- path="string" />
-</full-backup-content>
-</pre>
-
-<p>
- The following elements and attributes allow you to specify the files to include in, and exclude
- from, backup:
-</p>
-
-<ul>
- <li>
- <code><include></code>: Specifies a set of resources to
- back up, instead of having the system back up all data in your app by default. If you specify
- an <code><include></code> element, the system backs up <em>only the resources specified</em>
- with this element. You can specify multiple sets of resources to back up by using multiple
- <code><include></code> elements
- </li>
-
- <li>
- <code><exclude></code>: Specifies any data you want the system to exclude
- when it does a full backup. If you target the same set of resources with both the
- <code><include></code> and <code><exclude></code> elements,
- <code><exclude></code> takes precedence.
- </li>
-
- <li>
- <code>domain</code>: Specifies the type of resource you want to include in,
- or exclude from, backup. Valid values for this attribute include:
-
-
-
- <ul>
- <li>
- <code>root</code>: Specifies that the resource is in the app’s root directory.
- </li>
-
- <li>
- <code>file</code>: Specifies a resource in the directory returned by the
- {@link android.content.Context#getFilesDir getFilesDir()} method.
- </li>
-
- <li>
- <code>database</code>: Specifies a database that the
- {@link android.content.Context#getDatabasePath getDatabasePath()} method returns, or that
- the app interacts with via the {@link android.database.sqlite.SQLiteOpenHelper} class.
- </li>
-
- <li>
- <code>sharedpref</code>: Specifies a {@link android.content.SharedPreferences} object
- that the {@link android.content.Context#getSharedPreferences getSharedPreferences()}
- method returns.
- </li>
-
- <li>
- <code>external</code>: Specifies that the resource is in external storage, and corresponds
- to a file in the directory that the
- {@link android.content.Context#getExternalFilesDir getExternalFilesDir()} method returns.
- </li>
- </ul>
- </li>
- <li>
- <code>path</code>: Specifies the file path to a resource that you want to include in, or
- exclude from, backup.
- </li>
-
- </li>
-</ul>
-
-
-<h3 id="disabling">Disabling data backups</h3>
-
-<p>
- You can choose to prevent automatic backups of any of your app data by setting the
- <code>android:allowBackup</code> attribute to <code>false</code> in the {@code app} element of
- your manifest. This setting is illustrated in the following example:
-</p>
-
-<pre>
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.my.appexample">
- <uses-sdk android:minSdkVersion="23"/>
- <uses-sdk android:targetSdkVersion="23"/>
- <application ...
-<strong> android:allowBackup="false"></strong>
- </application>
- ...
-</manifest>
-</pre>
-
-<h2 id="previous-androids">Support Lower Versions of Android</h2>
-
-<p>There are two scenarios in which you may also need to support versions of Android lower
-than 6.0 (API level 23): You may be updating your existing app to take advantage of the
-new auto backup functionality in Android 6.0, while wanting
-to continue supporting earlier versions of Android. Or you may be releasing a new app, but
-want to make sure devices running on versions of Android predating 6.0 also have backup
-functionality.</p>
-
-<h3 id="updating">Updating an existing app to support auto backup</h3>
-
-<p>Earlier versions of Android supported a key/value-pair-based backup mechanism, in which the app
-defines a subclass of {@link android.app.backup.BackupAgent} and sets
-<a href="{@docRoot}guide/topics/manifest/application-element.html#agent">
-{@code android:backupAgent}</a> in its
-<a href="{@docRoot}guide/topics/manifest/application-element.html">app manifest</a>. If your app
-used this legacy approach, you can transition to full-data backups by adding the
-{@code android:fullBackupOnly="true"} attribute to the
-<a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application/>}</a>
-element in the manifest. When running on a device with Android 5.1
-(API level 22) or lower, your app ignores this value in the manifest, and continues performing
-backups in the previous manner.</p>
-
-<p>Even if you’re not using key/value backups, you can still use the approach described above to do
-any custom processing in {@link android.app.Activity#onCreate(android.os.Bundle) onCreate()}
-or {@link android.app.backup.BackupAgent#onFullBackup onFullBackup()}. You can also use that
-approach to receive a notification when a restore operation happens in
-{@link android.app.backup.BackupAgent#onRestoreFinished onRestoreFinished()}. If you want to retain
-the system's default implementation of
-<a href="#include-exclude">XML include/exclude rules handling</a>, call
-{@link android.app.backup.BackupAgent#onFullBackup super.onFullBackup()}.</p>
-
-<h3 id="lower-versions">Giving your new app support for lower versions of Android</h3>
-
-<p>If you are creating a new app that targets Android 6.0, but you also want to enable cloud backup
-for devices running on Android 5.1 (API level 22) and lower, you must also
-<a href="{@docRoot}training/backup/backupapi.html">implement the Backup API</a>.</p>
-
-<h2 id="testing">Test Backup Configuration</h2>
-
-<p>
- Once you have created a backup configuration, you should test it to make sure your app saves data
- and can restore it properly.
-</p>
-
-
-<h3>Enabling Backup Logging</h3>
-
-<p>
- To help determine how the backup feature is parsing your XML file, enable logging before
- performing a test backup:
-</p>
-
-<pre class="no-pretty-print">
-$ adb shell setprop log.tag.BackupXmlParserLogging VERBOSE
-</pre>
-
-<h3>Testing Backup</h3>
-
-<p>To manually run a backup, first initialize the Backup Manager by executing the following
- command:
-</p>
-
-<pre class="no-pretty-print">
-$ adb shell bmgr run
-</pre>
-
-<p>
- Next, manually back up your application using the following command. Use the
- <code><PACKAGE></code> parameter to specify the package name for your app:
-</p>
-
-<pre class="no-pretty-print">
-$ adb shell bmgr fullbackup <PACKAGE></pre>
-
-
-<h3>Testing restore</h3>
-
-<p>
- To manually initiate a restore after the system has backed up your app data, execute the following
- command, using the <code><PACKAGE></code> parameter to specify the package name for your
- app:
-</p>
-
-<pre class="noprettyprint">
-$ adb shell bmgr restore <PACKAGE>
-</pre>
-
-<p class="warning">
- <b>Warning:</b> This action stops your app and wipes its data before performing the restore
- operation.
-</p>
-
-<p>
- You can test automatic restore for your app by uninstalling and reinstalling your app. The app
- data is automatically restored from the cloud once the app installation is complete.
-</p>
-
-
-<h3>Troubleshooting backups</h3>
-
-<p>
- If backup fails, you can clear the backup data and associated metadata either by turning backup
- off and on in <strong>Settings > Backup</strong>, factory-resetting the device, or
- executing this command:
-</p>
-
-<pre>$ adb shell bmgr wipe <TRANSPORT> <PACKAGE></pre>
-
-<p>
- You must prepend <code>com.google.android.gms</code> to the {@code <TRANSPORT>} value.
- To get the list of <a href="{@docRoot}google/backup/index.html">transports</a>, execute the
- following command:
-</p>
-
-<pre>$ adb shell bmgr list transports</pre>
-
-<h2 id="gcm">Handle Google Cloud Messaging</h2>
-
- <p>
- For apps that use <a href="https://developers.google.com/cloud-messaging/gcm">Google Cloud
- Messaging</a> (GCM) for push notifications, backing up the registration
- token that Google Cloud Messaging registration returned can cause unexpected behavior in
- notifications for the restored app. This is because when a user installs your app on a new device,
- the app must <a href="https://developers.google.com/cloud-messaging/android/client#sample-register">
- query the GCM API for a new registration token</a>. If the old registration is present, because the
- system had backed it up and restored it, the app doesn't seek the new token. To prevent this issue
- from arising, exclude the registration token from the set of backed-up files.
- </p>
diff --git a/docs/html/training/backup/backupapi.jd b/docs/html/training/backup/backupapi.jd
deleted file mode 100644
index 2f3e939..0000000
--- a/docs/html/training/backup/backupapi.jd
+++ /dev/null
@@ -1,200 +0,0 @@
-page.title=Using the Backup API
-parent.title=Backing up App Data to the Cloud
-parent.link=index.html
-
-trainingnavtop=true
-
-next.title=Making the Most of Google Cloud Messaging
-next.link=gcm.html
-
-@jd:body
-
-<div id="tb-wrapper">
- <div id="tb">
- <h2>This lesson teaches you to</h2>
- <ol>
- <li><a href="#register">Register for the Android Backup Service</a></li>
- <li><a href="#manifest">Configure Your Manifest</a></li>
- <li><a href="#agent">Write Your Backup Agent</a></li>
- <li><a href="#backup">Request a Backup</a></li>
- <li><a href="#restore">Restore from a Backup</a></li>
- </ol>
- <h2>You should also read</h2>
- <ul>
- <li><a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a></li>
- <li><a href="{@docRoot}training/backup/autosyncapi.html">Configuring Auto Backup for Apps</a>
- (Android 6.0 (API level 23) and higher)</li>
- </ul>
- </div>
-</div>
-
-<p>When a user purchases a new device or resets their existing one, they might
-expect that when Google Play restores your app back to their device during the
-initial setup, the previous data associated with the app restores as well. On versions of Android
-prior to 6.0 (API level 23), app data is not restored by default, and all the user's accomplishments
-or settings in your app are lost.</p>
-<p>For situations where the volume of data is relatively light (less than a
-megabyte), like the user's preferences, notes, game high scores or other
-stats, the Backup API provides a lightweight solution. This lesson walks you
-through integrating the Backup API into your application, and restoring data to
-new devices using the Backup API.
-
-<p class="note">
-<strong>Note:</strong> Devices running Android 6.0 and higher
-<a href="{@docRoot}training/backup/autosyncapi.html">automatically back up</a>
-nearly all data by default.
-</p>
-
-<h2 id="register">Register for the Android Backup Service</h2>
-<p>This lesson requires the use of the <a
- href="{@docRoot}google/backup/index.html">Android Backup
- Service</a>, which requires registration. Go ahead and <a
- href="http://code.google.com/android/backup/signup.html">register here</a>. Once
-that's done, the service pre-populates an XML tag for insertion in your Android
-Manifest, which looks like this:</p>
-<pre>
-<meta-data android:name="com.google.android.backup.api_key"
-android:value="ABcDe1FGHij2KlmN3oPQRs4TUvW5xYZ" />
-</pre>
-<p>Note that each backup key works with a specific package name. If you have
-different applications, register separate keys for each one.</p>
-
-
-<h2 id="manifest">Configure Your Manifest</h2>
-<p>Use of the Android Backup Service requires two additions to your application
-manifest. First, declare the name of the class that acts as your backup agent,
-then add the snippet above as a child element of the Application tag. Assuming
-your backup agent is going to be called {@code TheBackupAgent}, here's an example of
-what the manifest looks like with this tag included:</p>
-
-<pre>
-<application android:label="MyApp"
- android:backupAgent="TheBackupAgent">
- ...
- <meta-data android:name="com.google.android.backup.api_key"
- android:value="ABcDe1FGHij2KlmN3oPQRs4TUvW5xYZ" />
- ...
-</application>
-</pre>
-<h2 id="agent">Write Your Backup Agent</h2>
-<p>The easiest way to create your backup agent is by extending the wrapper class
-{@link android.app.backup.BackupAgentHelper}. Creating this helper class is
-actually a very simple process. Just create a class with the same name as you
-used in the manifest in the previous step (in this example, {@code
-TheBackupAgent}),
-and extend {@code BackupAgentHelper}. Then override the {@link
-android.app.backup.BackupAgent#onCreate()}.</p>
-
-<p>Inside the {@link android.app.backup.BackupAgent#onCreate()} method, create a {@link
-android.app.backup.BackupHelper}. These helpers are
-specialized classes for backing up certain kinds of data. The Android framework
-currently includes two such helpers: {@link
-android.app.backup.FileBackupHelper} and {@link
-android.app.backup.SharedPreferencesBackupHelper}. After you create the helper
-and point it at the data you want to back up, just add it to the
-BackupAgentHelper using the {@link android.app.backup.BackupAgentHelper#addHelper(String, BackupHelper) addHelper()}
-method, adding a key which is used to
-retrieve the data later. In most cases the entire
-implementation is perhaps 10 lines of code.</p>
-
-<p>Here's an example that backs up a high scores file.</p>
-
-<pre>
-import android.app.backup.BackupAgentHelper;
-import android.app.backup.FileBackupHelper;
-
-
-public class TheBackupAgent extends BackupAgentHelper {
- // The name of the SharedPreferences file
- static final String HIGH_SCORES_FILENAME = "scores";
-
- // A key to uniquely identify the set of backup data
- static final String FILES_BACKUP_KEY = "myfiles";
-
- // Allocate a helper and add it to the backup agent
- @Override
- void onCreate() {
- FileBackupHelper helper = new FileBackupHelper(this, HIGH_SCORES_FILENAME);
- addHelper(FILES_BACKUP_KEY, helper);
- }
-}
-</pre>
-<p>For added flexibility, {@link android.app.backup.FileBackupHelper}'s
-constructor can take a variable number of filenames. You could just as easily
-have backed up both a high scores file and a game progress file just by adding
-an extra parameter, like this:</p>
-<pre>
-@Override
- void onCreate() {
- FileBackupHelper helper = new FileBackupHelper(this, HIGH_SCORES_FILENAME, PROGRESS_FILENAME);
- addHelper(FILES_BACKUP_KEY, helper);
- }
-</pre>
-<p>Backing up preferences is similarly easy. Create a {@link
-android.app.backup.SharedPreferencesBackupHelper} the same way you did a {@link
-android.app.backup.FileBackupHelper}. In this case, instead of adding filenames
-to the constructor, add the names of the shared preference groups being used by
-your application. Here's an example of how your backup agent helper might look if
-high scores are implemented as preferences instead of a flat file:</p>
-
-<pre>
-import android.app.backup.BackupAgentHelper;
-import android.app.backup.SharedPreferencesBackupHelper;
-
-public class TheBackupAgent extends BackupAgentHelper {
- // The names of the SharedPreferences groups that the application maintains. These
- // are the same strings that are passed to getSharedPreferences(String, int).
- static final String PREFS_DISPLAY = "displayprefs";
- static final String PREFS_SCORES = "highscores";
-
- // An arbitrary string used within the BackupAgentHelper implementation to
- // identify the SharedPreferencesBackupHelper's data.
- static final String MY_PREFS_BACKUP_KEY = "myprefs";
-
- // Simply allocate a helper and install it
- void onCreate() {
- SharedPreferencesBackupHelper helper =
- new SharedPreferencesBackupHelper(this, PREFS_DISPLAY, PREFS_SCORES);
- addHelper(MY_PREFS_BACKUP_KEY, helper);
- }
-}
-</pre>
-
-<p>You can add as many backup helper instances to your backup agent helper as you
-like, but remember that you only need one of each type. One {@link
-android.app.backup.FileBackupHelper} handles all the files that you need to back up, and one
-{@link android.app.backup.SharedPreferencesBackupHelper} handles all the shared
-preferencegroups you need backed up.
-</p>
-
-
-<h2 id="backup">Request a Backup</h2>
-<p>In order to request a backup, just create an instance of the {@link
-android.app.backup.BackupManager}, and call it's {@link
-android.app.backup.BackupManager#dataChanged()} method.</p>
-
-<pre>
-import android.app.backup.BackupManager;
-...
-
-public void requestBackup() {
- BackupManager bm = new BackupManager(this);
- bm.dataChanged();
-}
-</pre>
-
-<p>This call notifies the backup manager that there is data ready to be backed
-up to the cloud. At some point in the future, the backup manager then calls
-your backup agent's {@link
-android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput,
-ParcelFileDescriptor) onBackup()} method. You can make
-the call whenever your data has changed, without having to worry about causing
-excessive network activity. If you request a backup twice before a backup
-occurs, the backup only occurs once.</p>
-
-
-<h2 id="restore">Restore from a Backup</h2>
-<p>Typically you shouldn't ever have to manually request a restore, as it
-happens automatically when your application is installed on a device. However,
-if it <em>is</em> necessary to trigger a manual restore, just call the
-{@link android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()} method.</p>
diff --git a/docs/html/training/backup/index.jd b/docs/html/training/backup/index.jd
deleted file mode 100644
index 4449fde..0000000
--- a/docs/html/training/backup/index.jd
+++ /dev/null
@@ -1,47 +0,0 @@
-page.title=Backing up App Data to the Cloud
-page.tags=cloud,sync,backup
-
-trainingnavtop=true
-startpage=true
-
-@jd:body
-
-<div id="tb-wrapper">
-<div id="tb">
-
-<h2>Dependencies and prerequisites</h2>
-<ul>
- <li>Android 2.2 (API level 8) and higher</li>
-</ul>
-</div>
-</div>
-
-<p>Users often invest significant time and effort creating data and setting
-preferences within apps. Preserving that data for users if they replace a broken
-device or upgrade to a new one is an important part of ensuring a great user
-experience.</p>
-
-<p>This class covers techniques for backing up data to the cloud so that
-users can restore their data when recovering from a data loss (such as a factory
-reset) or installing your application on a new device.</p>
-
-<p>It is important to note that the API for cloud backup changed with the
-release of Android 6.0 (API level 23). For your app to support backup both
-on devices running Android 6.0, and those running Android 5.1 (API level
-22) and lower, you must implement both techniques that this class explains.</p>
-
-<h2>Lessons</h2>
-
-<dl>
- <dt><strong><a href="autosyncapi.html">Configuring Auto Backup for Apps</a></strong></dt>
- <dd>This lesson applies to Android 6.0 (API level 23) and higher. Learn how to accomplish
- seamless app data backup and restore with zero additional lines of application code.</dd>
-</dl>
-
-<dl>
- <dt><strong><a href="backupapi.html">Using the Backup API</a></strong></dt>
- <dd>This lesson applies to Android 5.1 (API level 22) and lower. Learn how to integrate the Backup
- API into your Android app, so all of that app's user data, such as preferences, notes, and high
- scores, updates seamlessly across all devices linked to that Google account.</dd>
-</dl>
-
diff --git a/docs/html/training/best-performance.jd b/docs/html/training/best-performance.jd
index 8ea6fd5..bb88e99 100644
--- a/docs/html/training/best-performance.jd
+++ b/docs/html/training/best-performance.jd
@@ -5,4 +5,9 @@
<p>These classes and articles help you build an app that's smooth, responsive,
-and uses as little battery as possible.</p>
\ No newline at end of file
+and uses as little battery as possible.</p>
+
+<p>Along with this section, you can find additional information about optimizing
+your app in the <a href="/topic/performance/index.html">Performance and
+Power</a> section.</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/location/geofencing.jd b/docs/html/training/location/geofencing.jd
index ce6ad55..046e99e 100644
--- a/docs/html/training/location/geofencing.jd
+++ b/docs/html/training/location/geofencing.jd
@@ -332,22 +332,39 @@
<p>This section outlines recommendations for using geofencing with the location
APIs for Android.</p>
-<h3>Reduce power consumption</h3>
+<h3>
+ Reduce power consumption
+</h3>
-<p>You can use the following techniques to optimize power consumption in your apps that use geofencing:</p>
+<p>
+ You can use the following techniques to optimize power consumption in your
+ apps that use geofencing:
+</p>
<ul>
-<li><p>Set the <a href="{@docRoot}android/reference/com/google/android/gms/location/Geofence.Builder.html#setNotificationResponsiveness(int)">
-notification responsiveness</a> to a higher value. Doing so improves power consumption by
-increasing the latency of geofence alerts. For example, if you set a responsiveness value of five
-minutes your app only checks for an entrance or exit alert once every five minutes.
-Setting lower values does not necessarily mean that users will be notified within that time period
-(for example, if you set a value of 5 seconds it may take a bit longer than that to receive the
-alert).</p></li>
-<li><p>Use a larger geofence radius for locations where a user spends a significant amount of time,
-such as home or work. While a larger radius doesn't directly reduce power consumption, it reduces
-the frequency at which the app checks for entrance or exit, effectively lowering overall power
-consumption.</p></li>
+ <li>
+ <p>
+ Set the <a href=
+ "https://developers.google.com/android/reference/com/google/android/gms/location/Geofence.Builder.html#setNotificationResponsiveness(int)">
+ notification responsiveness</a> to a higher value. Doing so improves
+ power consumption by increasing the latency of geofence alerts. For
+ example, if you set a responsiveness value of five minutes your app only
+ checks for an entrance or exit alert once every five minutes. Setting
+ lower values does not necessarily mean that users will be notified
+ within that time period (for example, if you set a value of 5 seconds it
+ may take a bit longer than that to receive the alert).
+ </p>
+ </li>
+
+ <li>
+ <p>
+ Use a larger geofence radius for locations where a user spends a
+ significant amount of time, such as home or work. While a larger radius
+ doesn't directly reduce power consumption, it reduces the frequency at
+ which the app checks for entrance or exit, effectively lowering overall
+ power consumption.
+ </p>
+ </li>
</ul>
<h3>Choose the optimal radius for your geofence</h3>
diff --git a/docs/html/training/monitoring-device-state/battery-monitoring.jd b/docs/html/training/monitoring-device-state/battery-monitoring.jd
index db75aaf..bab9c9c 100644
--- a/docs/html/training/monitoring-device-state/battery-monitoring.jd
+++ b/docs/html/training/monitoring-device-state/battery-monitoring.jd
@@ -141,10 +141,11 @@
condition by listening for {@link android.content.Intent#ACTION_BATTERY_LOW} and {@link
android.content.Intent#ACTION_BATTERY_OKAY}.</p>
-<pre><receiver android:name=".BatteryLevelReceiver">
-<intent-filter>
- <action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
- <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
+<pre>
+<receiver android:name=".BatteryLevelReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BATTERY_LOW"/>
+ <action android:name="android.intent.action.BATTERY_OKAY"/>
</intent-filter>
</receiver></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/training_toc.cs b/docs/html/training/training_toc.cs
index d0dccba..d2bf881 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -649,25 +649,7 @@
</li>
</ul>
</li>
-
<li class="nav-section">
- <div class="nav-section-header">
- <a href="<?cs var:toroot ?>training/backup/index.html"
- description=
- "How to sync and back up app and user data to remote web services in the
- cloud and how to restore the data back to multiple devices."
- >Backing up App Data to the Cloud</a>
- </div>
- <ul>
- <li><a href="<?cs var:toroot ?>training/backup/autosyncapi.html">
- Configuring Auto Backup
- </a>
- </li>
- <li><a href="<?cs var:toroot ?>training/backup/backupapi.html">
- Using the Backup API
- </a>
- </li>
- </ul>
<li><a href="<?cs var:toroot ?>training/cloudsave/conflict-res.html"
description=
"How to design a robust conflict resolution strategy for apps that save data to the cloud."
@@ -1888,6 +1870,12 @@
>Managing Your App's Memory</a>
</li>
<li>
+ <a href="<?cs var:toroot ?>training/articles/memory-overview.html"
+ description=
+ "How Android manages app process and memory allocation."
+ >Overview of Android Memory Management</a>
+ </li>
+ <li>
<a href="<?cs var:toroot ?>training/articles/perf-tips.html"
description=
"How to optimize your app's performance in various ways to improve its
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/docs/html/training/wearables/data-layer/messages.jd b/docs/html/training/wearables/data-layer/messages.jd
index ef9bfb1..8c4b730 100644
--- a/docs/html/training/wearables/data-layer/messages.jd
+++ b/docs/html/training/wearables/data-layer/messages.jd
@@ -10,12 +10,6 @@
<li><a href="#SendMessage">Send a Message</a></li>
<li><a href="#ReceiveMessage">Receive a Message</a></li>
</ol>
-<h2>Try it out</h2>
-<ul>
- <li>
- <a href="https://github.com/googlesamples/android-FindMyPhone/" class="external-link">FindMyPhone</a>
- </li>
-</ul>
</div>
</div>
@@ -27,6 +21,7 @@
<li>An arbitrary payload (optional)</li>
<li>A path that uniquely identifies the message's action</li>
</ul>
+
<p>
Unlike with data items, there is no syncing between the handheld and wearable apps.
Messages are a one-way communication mechanism that's good for remote procedure calls (RPC),
@@ -149,11 +144,9 @@
<a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>
to detect capability changes, you may want to override the
<a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onConnectedNodes(java.util.List<com.google.android.gms.wearable.Node>)"><code>onConnectedNodes()</code></a>
-method to listen to finer-grained connectivity details, such as when a wearable device switches
-from Wi-Fi to a Bluetooth connection to the handset. For an example implementation, see the
-<code>DisconnectListenerService</code> class in the
-<a href="https://github.com/googlesamples/android-FindMyPhone/" class="external-link">FindMyPhone</a>
-sample. For more information on how to listen for important events, see
+method to listen to finer-grained connectivity details, such as when a wearable
+device switches from Wi-Fi to a Bluetooth connection to the handset.
+For more information on how to listen for important events, see
<a href="{@docRoot}training/wearables/data-layer/events.html#Listen">Listen for Data Layer Events</a>.
</p>
diff --git a/docs/html/tv/_project.yaml b/docs/html/tv/_project.yaml
new file mode 100644
index 0000000..d2d41e1
--- /dev/null
+++ b/docs/html/tv/_project.yaml
@@ -0,0 +1,6 @@
+name: "TV"
+home_url: /tv/
+description: "Bring your apps, games, and content to the biggest screen in the house."
+content_license: cc3-apache2
+buganizer_id: 30209417
+parent_project_metadata_path: /about/_project.yaml
diff --git a/docs/html/wear/_project.yaml b/docs/html/wear/_project.yaml
index 2a94274..114cc5b 100644
--- a/docs/html/wear/_project.yaml
+++ b/docs/html/wear/_project.yaml
@@ -3,3 +3,4 @@
description: "Small, powerful devices, worn on the body. Useful information when you need it most."
content_license: cc3-apache2
buganizer_id: 30209417
+parent_project_metadata_path: /about/_project.yaml
diff --git a/docs/html/wear/images/partners/mkors.png b/docs/html/wear/images/partners/mkors.png
new file mode 100644
index 0000000..2f1e8ec
--- /dev/null
+++ b/docs/html/wear/images/partners/mkors.png
Binary files differ
diff --git a/docs/html/wear/images/partners/nixon.png b/docs/html/wear/images/partners/nixon.png
new file mode 100644
index 0000000..8674da2
--- /dev/null
+++ b/docs/html/wear/images/partners/nixon.png
Binary files differ
diff --git a/docs/html/wear/images/partners/polar.png b/docs/html/wear/images/partners/polar.png
new file mode 100644
index 0000000..2faeb06
--- /dev/null
+++ b/docs/html/wear/images/partners/polar.png
Binary files differ
diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd
index f9bdef5..1eb08be 100644
--- a/docs/html/wear/index.jd
+++ b/docs/html/wear/index.jd
@@ -267,6 +267,9 @@
<div class="col-4">
<img src="/wear/images/partners/mips.png" alt="MIPS">
</div>
+ <div class="col-4">
+ <img src="/wear/images/partners/mkors.png" alt=Michael Kors">
+ </div>
<div class="col-4">
<img src="/wear/images/partners/mobvoi.png" alt="Mobvoi">
</div>
@@ -277,6 +280,12 @@
<img src="/wear/images/partners/nb.png" alt="New Balance">
</div>
<div class="col-4">
+ <img src="/wear/images/partners/nixon.png" alt="Nixon">
+ </div>
+ <div class="col-4">
+ <img src="/wear/images/partners/polar.png" alt="Polar">
+ </div>
+ <div class="col-4">
<img src="/wear/images/partners/qualcomm.png" alt="Qualcomm">
</div>
<div class="col-4">
diff --git a/docs/html/wear/preview/_book.yaml b/docs/html/wear/preview/_book.yaml
index a231fb5..54d5442 100644
--- a/docs/html/wear/preview/_book.yaml
+++ b/docs/html/wear/preview/_book.yaml
@@ -8,18 +8,24 @@
- title: API Overview
path: /wear/preview/api-overview.html
section:
- - title: Notification Improvements
- path: /wear/preview/features/notifications.html
- - title: Input Method Framework
- path: /wear/preview/features/ime.html
- title: Complications
path: /wear/preview/features/complications.html
- title: Navigation and Actions
path: /wear/preview/features/ui-nav-actions.html
+ - title: Curved Layout
+ path: /wear/preview/features/wearable-recycler-view.html
+ - title: Notification Improvements
+ path: /wear/preview/features/notifications.html
- title: Bridging for Notifications
path: /wear/preview/features/bridger.html
+ - title: Input Method Framework
+ path: /wear/preview/features/ime.html
- title: Wrist Gestures
path: /wear/preview/features/gestures.html
+ - title: Standalone apps
+ path: /wear/preview/features/standalone-apps.html
+ - title: App Distribution
+ path: /wear/preview/features/app-distribution.html
- title: Get Started
path: /wear/preview/start.html
diff --git a/docs/html/wear/preview/_project.yaml b/docs/html/wear/preview/_project.yaml
new file mode 100644
index 0000000..4f7083e
--- /dev/null
+++ b/docs/html/wear/preview/_project.yaml
@@ -0,0 +1,6 @@
+name: "Wear Preview"
+home_url: /wear/preview/
+description: "Small, powerful devices, worn on the body. Useful information when you need it most."
+content_license: cc3-apache2
+buganizer_id: 30209417
+parent_project_metadata_path: /wear/_project.yaml
diff --git a/docs/html/wear/preview/api-overview.jd b/docs/html/wear/preview/api-overview.jd
index 0b3ac6b..f4612a2 100644
--- a/docs/html/wear/preview/api-overview.jd
+++ b/docs/html/wear/preview/api-overview.jd
@@ -13,15 +13,16 @@
<ol>
<li><a href="#complications">Complications</a></li>
<li><a href="#drawers">Navigation and Action Drawers</a></li>
+ <li><a href="#wrv">Curved Layout</a></li>
</ol>
</li>
<li><a href="#notify">Notifications and Input</a>
<ol>
- <li><a href="#expanded">Expanded Notification</a></li>
- <li><a href="#messaging">Messaging Style Notification</a></li>
+ <li><a href="#expanded">Expanded Notifications</a></li>
+ <li><a href="#messaging">Messaging Style Notifications</a></li>
+ <li><a href="#inline-action">Inline Action</a></li>
<li><a href="#smart-replies">Smart Reply</a></li>
- <li><a href="#content-action">Notification Content Action</a>
<li><a href="#remote-input">Remote Input</a></li>
<li><a href="#bridging">Bridging Mode</a></li>
<li><a href="#imf">Input Method Framework</a></li>
@@ -40,308 +41,447 @@
</div>
</div>
+ <p>
+ Wear 2.0 is still in active development, but you can try it as part of
+ the Wear 2.0 Developer Preview. The sections below highlight some of the
+ new features for developers.
+ </p>
+ <h2 id="ui">
+ User Interface Improvements
+ </h2>
-<p>
- The Android Wear Preview API is still in active development, but you can try
- it now as part of the Wear 2.0 Developer Preview. The sections below
- highlight some of the new features for Android Wear developers.
-</p>
+ <p>
+ The preview introduces powerful additions to the user interface, opening
+ up exciting possibilities to developers.
+ </p>
+ <h3 id="complications">
+ Complications
+ </h3>
-<h2 id="ui">User Interface Improvements</h2>
+ <img src="{@docRoot}wear/preview/images/complications-main-image.png"
+ height="320" style="float:right;margin:10px 0 0 40px">
+ <p>
+ A <a href=
+ "https://en.wikipedia.org/wiki/Complication_(horology)">complication</a>
+ is a feature of a watch face that displays more than hours and minutes,
+ such as a battery indicator or a step counter. The Complications API thus
+ helps watch face developers create visual features and the data
+ connections they require.
+ </p>
-<p>
- The preview introduces powerful additions to the user interface, opening up
- exciting possibilities to developers. A complication
- is any feature in a watch face that displays more than hours and
- minutes. With the Complications API, watch faces can display extra information
- and separate apps can expose complication data. The navigation and action
- drawers provide users with new ways to interact with apps.
-</p>
+ <p>
+ Watch faces that use this API can display extra information without
+ needing code for getting the underlying data. Data providers can supply
+ data to any watch face using the API.
+ </p>
+ <p>
+ For information about this API, see <a href=
+ "{@docRoot}wear/preview/features/complications.html">Watch Face
+ Complications</a>.
+ </p>
-<h3 id="complications">Complications</h3>
-<img src="{@docRoot}wear/preview/images/complications-main-image.png"
- height="320" style="float:right;margin:10px 0 0 40px" />
+ <h3 id="drawers">
+ Navigation and Action Drawers
+ </h3>
-<p>
- A <a href=
- "https://en.wikipedia.org/wiki/Complication_(horology)">complication</a> is a
- feature of a watch face that displays more than hours and minutes, such as a
- battery indicator or a step counter. The Complications API thus helps watch face
- developers create visual features and the data connections they
- require.
-</p>
+ <p>
+ Wear 2.0 introduces two new widgets, navigation drawer and action drawer.
+ These widgets give your users new ways to interact with your app. The
+ navigation drawer appears at the top of the screen and allows users to
+ navigate between app views. The action drawer appears at the bottom of
+ the screen and allows users to choose from a list of actions associated
+ with the current usage context. These drawers are accessible to users
+ when they edge swipe from the top or bottom of the screen; they peek when
+ users scroll in an opposite direction.
+ </p>
-<p>
- Watch faces that use this API can display extra information without needing
- code for getting the underlying data. Data providers can supply data to any
- watch face using the API.
-</p>
+ <div class="cols">
+ <div class="col-2of6">
+ <img src="{@docRoot}wear/preview/images/nav_drawer.gif" height="240"
+ alt="" style="padding:.5em">
+ </div>
-<p>For information about this API,
-see <a href="{@docRoot}wear/preview/features/complications.html">
- Watch Face Complications</a>.
-</p>
+ <div class="col-2of6">
+ <img src="{@docRoot}wear/preview/images/action_drawer.gif" height="240"
+ alt="" style="padding:.5em;">
+ </div>
+ </div>
-<h3 id="drawers">Navigation and Action drawers</h3>
+ <p>
+ To learn how to add these widgets to your app, see <a href=
+ "{@docRoot}wear/preview/features/ui-nav-actions.html">Wear Navigation and
+ Actions</a>.
+ </p>
-<p>Wear 2.0 introduces two new widgets, navigation drawer and action drawer. These
- widgets give your users new ways to interact with your app. The navigation drawer
- appears at the top of the screen and allows users to navigate between app views.
- The action drawer appears at the bottom of the screen and allows users to choose
- from a list of actions associated with the current usage context. These drawers
- are accessible to users when they edge swipe from the top or bottom of the
- screen; they peek when users scroll in an opposite direction.
-</p>
+ <h3 id="wrv">
+ Curved Layout
+ </h3>
-<div class="cols">
- <div class="col-2of6">
- <img src="{@docRoot}wear/preview/images/nav_drawer.gif"
- height="240" alt="" style="padding:.5em">
- </div>
- <div class="col-2of6">
- <img src="{@docRoot}wear/preview/images/action_drawer.gif"
- height="240" alt="" style="padding:.5em;">
- </div>
-</div>
+ <p>
+ Wear 2.0 introduces the <code>WearableRecyclerView</code> class for
+ displaying and manipulating a vertical list of items,
+ optimized for round displays.
+ </p>
-<p>
- To learn how to add these widgets to your app, see
- <a href="{@docRoot}wear/preview/features/ui-nav-actions.html">
- Wear Navigation and Actions</a>.
-</p>
+ <p>
+ The key features include the following:
+ </p>
+ <ul>
+ <li>A curved layout on round devices
+ </li>
-<h2 id="notify">Notifications and Input</h2>
+ <li>A circular scrolling gesture, which can be toggled on and off
+ </li>
+ </ul>
-<p>In Wear 2.0, we’ve redesigned the key experiences on the watch to be even more
- intuitive and provide users new ways to respond to messages. Some of the highlights
- are below; for a complete list of changes, see
- <a href="{@docRoot}wear/preview/features/notifications.html">Notification Changes in Wear 2.0</a>.
+ <p>
+ To learn how to create a curved layout optimized for round devices, see
+ <a href=
+ "https://developer.android.com/wear/preview/features/wearable-recycler-view.html">
+ Curved Layout</a>.
+ </p>
+ <h2 id="notify">
+ Notifications and Input
+ </h2>
-<img src="{@docRoot}wear/preview/images/expanded_diagram.png" height="340"
- style="float:left;margin:10px 20px 0 0" />
-<h3 id="expanded">Expanded notifications</h3>
+ <p>
+ In Wear 2.0, we’ve redesigned the key experiences on the watch to be even
+ more intuitive and provide users new ways to respond to messages. Some of
+ the highlights are below; for a complete list of changes, see <a href=
+ "{@docRoot}wear/preview/features/notifications.html">Notification Changes
+ in Wear 2.0</a>. <img src=
+ "{@docRoot}wear/preview/images/expanded_diagram.png" height="340" style=
+ "float:left;margin:10px 20px 0 0">
+ </p>
-<p>
- When a user taps on a notification that is bridged from the phone to the
- watch or that lacks a
- <a href="{@docRoot}reference/android/support/v4/app/NotificationCompat.Builder.html#setContentIntent(android.app.PendingIntent)">
- {@code contentIntent}</a>, the user will be taken to the expanded view of
- that notification. When you <a href=
- "{@docRoot}training/wearables/notifications/pages.html">specify additional
- content pages</a> and actions for a notification, those are available to the
- user within the expanded notification. Each expanded notification follows
- <a href="https://google.com/design/spec-wear">Material Design for Android
- Wear</a>, so the user gets an app-like experience.
-</p>
+ <h3 id="expanded">
+ Expanded Notifications
+ </h3>
+ <p>
+ When a user taps on a notification that is bridged from the phone to the
+ watch or that lacks a <a href=
+ "{@docRoot}reference/android/support/v4/app/NotificationCompat.Builder.html#setContentIntent(android.app.PendingIntent)">
+ {@code contentIntent}</a>, the user will be taken to the expanded view of
+ that notification. When you <a href=
+ "{@docRoot}training/wearables/notifications/pages.html">specify
+ additional content pages</a> and actions for a notification, those are
+ available to the user within the expanded notification. Each expanded
+ notification follows <a href=
+ "https://google.com/design/spec-wear">Material Design for Android
+ Wear</a>, so the user gets an app-like experience.
+ </p>
-<h3 id="messaging">Messaging Style notification</h3>
-<p> If you have a chat messaging app, your notifications should use
-{@code Notification.MessagingStyle}, which is new in Android 6.0. Wear 2.0 uses
-the chat messages included in a
-<a href="{@docRoot}preview/features/notification-updates.html#style">{@code MessagingStyle}</a>
- notification
-(see {@code addMessage()}) to provide a rich chat app-like experience in the
-expanded notification.
-</p>
+ <h3 id="messaging">
+ Messaging Style Notifications
+ </h3>
+ <p>
+ If you have a chat messaging app, your notifications should use {@code
+ Notification.MessagingStyle}, which is new in Android 6.0. Wear 2.0 uses
+ the chat messages included in a <a href=
+ "{@docRoot}preview/features/notification-updates.html#style">{@code
+ MessagingStyle}</a> notification (see {@code addMessage()}) to provide a
+ rich chat, app-like experience in the expanded notification.
+ </p>
-<h3 id="smart-replies">Smart Reply</h3>
+ <h3 id="inline-action">
+ Inline Action
+ </h3>
-<p>Android Wear 2.0 introduces support for Smart Reply in
-<a href="{@docRoot}wear/preview/features/notifications.html#messaging">{@code MessagingStyle}</a>
- notifications. Smart Reply provides the user with contextually relevant,
- touchable choices in the expanded notification and in
- <a href="{@docRoot}reference/android/app/RemoteInput.html">{@code RemoteInput}</a>.
-</p>
+ <p>
+ Wear 2.0 enables you to add an inline action within the notification
+ stream so that users can quickly take an action on a notification.
+ Examples of good use cases for an inline action within a notification stream
+ include replying to a text message, stopping a fitness activity, or
+ archiving an email message.
+ </p>
-<p>By enabling Smart Reply for your {@code MessagingStyle} notifications, you provide
-users a fast (single tap), discreet (no speaking aloud), and reliable way to respond
- to chat messages they receive.
- </p>
+ <p>
+ To learn how to add an inline action to your notification stream, see
+ <a href=
+ "https://developer.android.com/wear/preview/features/notifications.html#inline">
+ Inline Action</a>.
+ </p>
+ <h3 id="smart-replies">
+ Smart Reply
+ </h3>
-<img src="{@docRoot}wear/preview/images/remoteinput.png" height="350"
- style="float:right;margin:10px 0 0 40px" />
+ <p>
+ Android Wear 2.0 introduces support for Smart Reply in <a href=
+ "{@docRoot}wear/preview/features/notifications.html#messaging">{@code
+ MessagingStyle}</a> notifications. Smart Reply provides the user with
+ contextually relevant, touchable choices in the expanded notification and
+ in <a href="{@docRoot}reference/android/app/RemoteInput.html">{@code
+ RemoteInput}</a>.
+ </p>
-<h3 id="remote-input">Remote Input</h3>
+ <p>
+ By enabling Smart Reply for your {@code MessagingStyle} notifications,
+ you provide users a fast (single tap), discreet (no speaking aloud), and
+ reliable way to respond to chat messages they receive.
+ </p>
-<p>Wear 2.0 users can choose between various input options from
-<a href="{@docRoot}reference/android/app/RemoteInput.html">Remote Input</a>.
- These options include:
-</p>
-<ul>
-<li>Dictation</li>
-<li>Emoji</li>
-<li>Canned responses</li>
-<li>Smart Reply</i>
-<li>Default IME </i>
-</ul>
+ <img src="{@docRoot}wear/preview/images/remoteinput.png" height="350"
+ style="float:right;margin:10px 0 0 40px">
-<p>
-For messaging notifications with Smart Reply, the system-generated Smart Reply
- appears within <a href="{@docRoot}reference/android/app/RemoteInput.html">{@code RemoteInput}</a>
- above the developer-provided list of canned responses.
- You can also use the
- <a href="{@docRoot}reference/android/app/RemoteInput.Builder.html#setChoices(java.lang.CharSequence[])">setChoices()</a>
- method in the {@code RemoteInput} API to enable users to select from a list
- of canned responses.
-</p>
+ <h3 id="remote-input">
+ Remote Input
+ </h3>
-<h3 id="bridging"> Bridging Mode </h3>
-<p>By default, notifications are
-<a href="{@docRoot}training/wearables/notifications/index.html">
-bridged</a> (shared) from an app on a companion phone
-to the watch. Since a phone app and a standalone watch app may be sources of the
- same notifications, the Android Wear 2.0 Preview includes a Bridging mode feature.
- Developers can begin planning to change the behavior of notifications with the
- following:
-</p>
+ <p>
+ Wear 2.0 users can choose between various input options from <a href=
+ "{@docRoot}reference/android/app/RemoteInput.html">Remote Input</a>.
+ These options include:
+ </p>
-<ul>
-<li>Specifying in the standalone app's Android manifest file that notifications from
- the corresponding phone app should not be bridged to the watch. </li>
-<li>Setting a dismissal ID so notification dismissals (by users) are synced across
-devices.</li>
-</ul>
+ <ul>
+ <li>Dictation
+ </li>
-<p>For an example of how to use this feature, see <a href="{@docRoot}wear/preview/features/bridger.html">
-Bridging Mode for Notifications</a>.</p>
+ <li>Emoji
+ </li>
-<h3 id="imf">Input Method Framework</h3>
+ <li>Canned responses
+ </li>
-<p>Wear 2.0 extends the Android input method framework (IMF) to Android Wear.
-This allows users to enter text on Wear using the system default IME or third party
- IMEs. The Wear IME lets the user enter text via gesture typing as well as tapping
- individual keys. The IMF APIs used for Wear devices are the same as other form
- factors, though usage is slightly different due to limited screen real estate.
-</p>
+ <li>Smart Reply
+ </li>
-<p>Wear provides user settings on the watch that let the user:</p>
-<ul>
-<li>Enable multiple IMEs from the list of installed IMEs.</li>
-<li>Set a single default IME from the list of enabled IMEs.</li>
-<li>Change languages for various IMEs.</li>
-</ul>
+ <li>Default IME
+ </li>
+ </ul>
-<p>To learn how to create an IME for Wear, see <a href="{@docRoot}wear/preview/features/ime.html">
-Input Method Framework</a>.
-</p>
+ <p>
+ For messaging notifications with Smart Reply, the system-generated Smart
+ Reply appears within <a href=
+ "{@docRoot}reference/android/app/RemoteInput.html">{@code
+ RemoteInput}</a> above the developer-provided list of canned responses.
+ You can also use the <a href=
+ "{@docRoot}reference/android/app/RemoteInput.Builder.html#setChoices(java.lang.CharSequence[])">
+ setChoices()</a> method in the {@code RemoteInput} API to enable users to
+ select from a list of canned responses.
+ </p>
-<h3 id="wrist-gestures">Wrist Gestures</h3>
+ <h3 id="bridging">
+ Bridging Mode
+ </h3>
-<p>
- Wrist gestures can enable quick, one-handed interactions with your app
- when use of a touch screen is inconvenient. The following
- <a href="https://support.google.com/androidwear/answer/6312406">wrist gestures</a>
- are available for use by apps:
-</p>
+ <p>
+ By default, notifications are <a href=
+ "{@docRoot}training/wearables/notifications/index.html">bridged</a>
+ (shared) from an app on a companion phone to the watch. Since a phone app
+ and a standalone watch app may be sources of the same notifications, the
+ Android Wear 2.0 Preview includes a Bridging mode feature.
+ </p>
-<ul>
- <li>Flick wrist out</li>
- <li>Flick wrist in</li>
-</ul>
+ <p>
+ For information about this feature, see <a href=
+ "{@docRoot}wear/preview/features/bridger.html">Bridging Mode for
+ Notifications</a>.
+ </p>
-<p>For more information, see
-<a href="{@docRoot}wear/preview/features/gestures.html">
- Wrist Gestures</a>.
-</p>
+ <h3 id="imf">
+ Input Method Framework
+ </h3>
-<h2 id="stand-alone">Standalone Devices</h2>
+ <p>
+ Wear 2.0 extends the Android input method framework (IMF) to Android
+ Wear. This allows users to enter text on Wear using the system default
+ IME or third party IMEs. The Wear IME lets the user enter text via
+ gesture typing as well as tapping individual keys. The IMF APIs used for
+ Wear devices are the same as other form factors, though usage is slightly
+ different due to limited screen real estate.
+ </p>
-<p>Standalone watches will enable Android Wear apps to work independently of phone
- apps. This means your app can continue to offer full functionality even if the
- paired phone is far away or turned off. </p>
+ <p>
+ Wear provides user settings on the watch that let the user:
+ </p>
-<h3 id="wear-apk">Wear-Specific APKs</h3>
+ <ul>
+ <li>Enable multiple IMEs from the list of installed IMEs.
+ </li>
-<p>For delivery to a watch, an Android Wear app is currently embedded in its corresponding
-phone app. This delivery method can result in an increased download size for users,
- regardless of whether they have an Android Wear device.
-</p>
+ <li>Set a single default IME from the list of enabled IMEs.
+ </li>
-<p>With standalone devices, the
-<a href ="{@docRoot}google/play/publishing/multiple-apks.html">Multi-APK</a>
- delivery method will be used. Developers will have the ability to release Android
- Wear apps independently of the corresponding phone apps. Please stay tuned for
- more information about this change.
-</p>
+ <li>Change languages for various IMEs.
+ </li>
+ </ul>
-<h3 id="network">Network Access</h3>
+ <p>
+ To learn how to create an IME for Wear, see <a href=
+ "{@docRoot}wear/preview/features/ime.html">Input Method Framework</a>.
+ </p>
-<p>Since Android Wear apps will work independently of phone apps, Android Wear's
- network access will no longer require the
- <a href="{@docRoot}training/wearables/data-layer/index.html">
- Wearable Data Layer API</a>. Android Wear apps will have the ability to make
- their own network requests. Additionally, they will be able to directly use
- Google Cloud Messaging.
-</p>
+ <h3 id="wrist-gestures">
+ Wrist Gestures
+ </h3>
-<p>No APIs for network access or GCM are specific to Android Wear; refer to the
-existing documentation about
-<a href="{@docRoot}training/basics/network-ops/connecting.html">
-Connecting to the Network</a> and
-<a href="https://developers.google.com/cloud-messaging/">Cloud Messaging</a>.
-</p>
+ <p>
+ Wrist gestures can enable quick, one-handed interactions with your app
+ when use of a touch screen is inconvenient. The following <a href=
+ "https://support.google.com/androidwear/answer/6312406">wrist
+ gestures</a> are available for use by apps:
+ </p>
-<p>We recommend using the following libraries:</p>
-<ul>
-<li><a href="{@docRoot}reference/android/app/job/JobScheduler.html">
-JobScheduler</a> for asynchronous jobs, including polling at regular intervals
-</li>
-<li>Multi-networking APIs if you need to connect to specific network types; see
-the <a href="{@docRoot}about/versions/android-5.0.html#Wireless">
-Multiple Network Connections</a>
-</li>
-</ul>
+ <ul>
+ <li>Flick wrist out
+ </li>
-<p>You will still be able to use the
-<a href="{@docRoot}training/wearables/data-layer/index.html">
- Wearable Data Layer API</a> to communicate with a phone app.
- However, use of this API to connect to a network will be discouraged.
- </p>
+ <li>Flick wrist in
+ </li>
+ </ul>
+ <p>
+ For more information, see <a href=
+ "{@docRoot}wear/preview/features/gestures.html">Wrist Gestures</a>.
+ </p>
-<h3 id="auth">Authentication</h3>
+ <h2 id="stand-alone">
+ Standalone Devices
+ </h2>
-<p>Since Android Wear apps will work independently of phone apps, Android Wear's
- authentication capabilities will be more powerful; apps will have new ways to
- authenticate.</p>
+ <p>
+ Standalone watches enable Android Wear apps to work independently of
+ phone apps. This means your app can continue to offer full functionality
+ even if the paired phone is far away or turned off.
+ </p>
-<h4>Users can enter a username and password on a watch</h4>
+ <h3 id="wear-apk">
+ Wear-Specific APKs
+ </h3>
-<p>Google Keyboard will be standard on Android Wear, allowing for direct text
-entry. This feature will work as expected with standard
-<a href="{@docRoot}reference/android/widget/EditText.html">EditText widgets</a>.
-For passwords, the {@code textPassword} attribute will be used.</p>
+ <p>
+ For delivery to a watch, an Android Wear app is currently embedded in its
+ corresponding phone app. This delivery method can result in an increased
+ download size for users, regardless of whether they have an Android Wear
+ device.
+ </p>
-<h4>Utilizing Account Manager</h4>
+ <p>
+ For information about planning and building your standalone app
+ for Wear 2.0, see <a href=
+ "https://developer.android.com/wear/preview/features/standalone-apps.html">
+ Standalone Apps</a>.
+ </p>
-<p>Android Wear will include the
-<a href="{@docRoot}reference/android/accounts/AccountManager.html">
-AccountManager</a>, which will be accessible for syncing and storing account
-data, as it is on an Android phone.</p>
+ <p>
+ For information about distributing your app, see <a href=
+ "https://developer.android.com/wear/preview/features/app-distribution.html">
+ App Distribution</a>.
+ </p>
-<h4>Authentication tokens can be passed over the Wearable Data Layer</h4>
+ <h3 id="network">
+ Network Access
+ </h3>
-<p>For Android-paired watches (only), a phone securely
-transfers authentication credentials to a watch app via the
-<a href="{@docRoot}training/wearables/data-layer/index.html">
-Wearable Data Layer API</a>. The credentials can be transferred as
-messages or data items.</p>
+ <p>
+ Since Android Wear apps will work independently of phone apps, Android
+ Wear's network access will no longer require the <a href=
+ "{@docRoot}training/wearables/data-layer/index.html">Wearable Data Layer
+ API</a>. Android Wear apps will have the ability to make their own
+ network requests. Additionally, they will be able to directly use Google
+ Cloud Messaging. For more information, see
+ <a href=
+ "https://developer.android.com/wear/preview/features/standalone-apps.html#network_access">
+ Network Access and Cloud Messaging</a>.
+ </p>
-<p>If your watch app needs to determine if your phone app is installed, you can
-advertise a capability on the phone app and retrieve the capability on the
-watch. For more information, see the following sections of
-<a href="{@docRoot}training/wearables/data-layer/messages.html">
-Sending and Receiving Messages</a>:</p>
+ <p>
+ No APIs for network access or GCM are specific to Android Wear; refer to
+ the existing documentation about <a href=
+ "{@docRoot}training/basics/network-ops/connecting.html">Connecting to the
+ Network</a> and <a href=
+ "https://developers.google.com/cloud-messaging/">Cloud Messaging</a>.
+ </p>
-<ul>
- <li>Advertise Capabilities</li>
- <li>Retrieve the Nodes with the Required Capabilities</li>
-</ul>
+ <p>
+ We recommend using the following libraries:
+ </p>
+
+ <ul>
+ <li>
+ <a href=
+ "{@docRoot}reference/android/app/job/JobScheduler.html">JobScheduler</a>
+ for asynchronous jobs, including polling at regular intervals
+ </li>
+
+ <li>Multi-networking APIs, to connect to specific network
+ types; see <a href=
+ "{@docRoot}about/versions/android-5.0.html#Wireless">Multiple Network
+ Connections</a>
+ </li>
+ </ul>
+
+ <p>
+ The <a href=
+ "{@docRoot}training/wearables/data-layer/index.html">Wearable Data Layer
+ API</a> is available to communicate with a phone app.
+ However, use of this API to connect to a network will be discouraged.
+ </p>
+
+ <h3 id="auth">
+ Authentication
+ </h3>
+
+ <p>
+ Since Android Wear apps will work independently of phone apps, Android
+ Wear's authentication capabilities will be more powerful; apps will have
+ new ways to authenticate.
+ </p>
+
+ <h4>
+ Users can enter a username and password on a watch
+ </h4>
+
+ <p>
+ Google Keyboard will be standard on Android Wear, allowing for direct
+ text entry. This feature will work as expected with standard <a href=
+ "{@docRoot}reference/android/widget/EditText.html">EditText widgets</a>.
+ For passwords, the {@code textPassword} attribute will be used.
+ </p>
+
+ <h4>
+ Utilizing Account Manager
+ </h4>
+
+ <p>
+ Android Wear will include the <a href=
+ "{@docRoot}reference/android/accounts/AccountManager.html">AccountManager</a>,
+ which will be accessible for syncing and storing account data, as it is
+ on an Android phone.
+ </p>
+
+ <h4>
+ Authentication tokens can be passed over the Wearable Data Layer
+ </h4>
+
+ <p>
+ For Android-paired watches (only), a phone securely transfers
+ authentication credentials to a watch app via the <a href=
+ "{@docRoot}training/wearables/data-layer/index.html">Wearable Data Layer
+ API</a>. The credentials can be transferred as messages or data items.
+ </p>
+
+ <p>
+ If your watch app needs to determine if your phone app is installed, you
+ can advertise a capability on the phone app and retrieve the capability
+ on the watch. For more information, see the following sections of
+ <a href="{@docRoot}training/wearables/data-layer/messages.html">Sending
+ and Receiving Messages</a>:
+ </p>
+
+ <ul>
+ <li>Advertise Capabilities
+ </li>
+
+ <li>Retrieve the Nodes with the Required Capabilities
+ </li>
+ </ul>
diff --git a/docs/html/wear/preview/behavior-changes.jd b/docs/html/wear/preview/behavior-changes.jd
index 0214622..c93d337 100644
--- a/docs/html/wear/preview/behavior-changes.jd
+++ b/docs/html/wear/preview/behavior-changes.jd
@@ -22,6 +22,8 @@
<ul>
<li><a href="#activity-dismissal">Activity Dismissal</a></li>
+ <li><a href="#invalid-fields">Invalid Fields for a Complication Type</a></li>
+ <li><a href="#empty">Complication Types for Empty Data</a></li>
</ul>
</div>
@@ -61,3 +63,44 @@
<a href="{@docRoot}wear/preview/features/ui-nav-actions.html">navigation
drawers</a>.
</p>
+
+<h2 id="invalid-fields">Invalid Fields for a Complication Type</h2>
+
+<p>
+ When a watch face uses the <a href="{@docRoot}wear/preview/features/complications.html">
+ Complications API</a>, the watch face requests data from a chosen provider.
+ A <code>ComplicationData</code> object, which contains
+ complication types, is returned.
+</p>
+
+<p>
+ A complication type determines the
+ kinds of data that a watch face can render. This section describes
+ a behavior change related to the <code>ComplicationData</code> object.
+</p>
+
+<p>
+ Starting with
+ <a href="https://developer.android.com/wear/preview/support.html#dp3">
+ Developer Preview 3</a>, when a watch face requests a field that is invalid
+ for a complication type, a default value for the field is returned.
+ For example, if a watch face tries to access a <code>Long text</code>
+ field in a <code>SHORT_TEXT</code> type, the default value for the
+ <code>Long text</code> field is returned.
+ In previous releases, such a request for an invalid field
+ (for a type) resulted in an exception.
+</p>
+
+<h2 id="empty">Complication Types for Empty Data</h2>
+
+<p>
+ Starting with
+ <a href="https://developer.android.com/wear/preview/support.html#dp3">
+ Developer Preview 3</a>, the complication types used for "empty" data are
+ changed. Apps that use the Complications API
+ may need to be updated to use
+ <code>TYPE_NO_DATA</code>. See the information
+ about <code>TYPE_NO_DATA</code> in the
+ <a href="{@docRoot}wear/preview/features/complications.html#types_and_fields">
+ Types and fields</a> section.
+</p>
diff --git a/docs/html/wear/preview/downloads.jd b/docs/html/wear/preview/downloads.jd
index 4bc401b..bfa384b 100644
--- a/docs/html/wear/preview/downloads.jd
+++ b/docs/html/wear/preview/downloads.jd
@@ -171,7 +171,9 @@
<li>
<a href="#set_up_a_watch">Set Up a Watch</a>
</li>
-
+ <li>
+ <a href="#set_up_a_phone">Set Up a Phone</a>
+ </li>
<li>
<a href="#set_up_an_emulator">Set Up an Emulator</a>
</li>
@@ -180,7 +182,7 @@
</div>
<p>
- You can run and test your app with the Android Wear 2.0 Developer Preview
+ You can run and test your app with the Android Wear 2.0 Preview
in either of these ways:
</p>
@@ -237,6 +239,13 @@
following tables and flash it to the corresponding device.
</p>
+ <p class="caution"><strong>Caution:</strong>
+ After you flash an image to a watch, follow the steps for
+ <a href="#set_up_a_phone">setting up a phone</a> with the beta version of
+ the Android Wear companion app. To use a Wear 2.0 image on a watch,
+ you must have the beta companion app on a paired phone.
+ </p>
+
<p>
To restore your device to its original state during the preview,
you can flash the appropriate retail system image, below, to the device.
@@ -266,9 +275,9 @@
<td>
Preview image for testing
</td>
- <td><a href="#top" onclick="onDownload(this)">nemo-nvd83h-factory-48ac950c.tgz</a><br>
- MD5: dd351884cce9fb5bf1bdec0a8e5f56e3<br>
- SHA-1: 48ac950c48faef96a7770e3c1acb56d23a28d859
+ <td><a href="#top" onclick="onDownload(this)">nemo-nve68j-factory-302a33ea.tgz</a><br>
+ MD5: ddfccc3e050c7e2db8d657c82f7d6291<br>
+ SHA-1: 302a33eac348c401fcb165bad4b9aaa40c7beb2b
</td>
</tr>
@@ -276,9 +285,9 @@
<td>
Non-preview image (for after testing)
</td>
- <td><a href="#top" onclick="onDownload(this)">nemo-mnc40x-factory-fa528bec.tgz</a><br>
- MD5: 0b8ba3653d5a93cb854f4d7409d7b6c9<br>
- SHA-1: fa528bec8aba3bf6c7d901ba63cd6ea0a08dbeb0
+ <td><a href="#top" onclick="onDownload(this)">nemo-mfd18l-factory-3faf6f2d.tgz</a><br>
+ MD5: f3a0090c0e99da82ad095b5d2a9acc6d<br>
+ SHA-1: 3faf6f2d7f422a17a5f6c54cf5e1d2c5622689b0
</td>
</tr>
@@ -307,18 +316,18 @@
<td>
Preview image for testing
</td>
- <td><a href="#top" onclick="onDownload(this)">sturgeon-nvd83h-factory-cb5a11ab.tgz</a><br>
- MD5: 38c1047992b1d28f6833d9f6c8470cdc<br>
- SHA-1: cb5a11ab0260ea3ca7da5894e73e41f70357da6b
+ <td><a href="#top" onclick="onDownload(this)">sturgeon-nve68j-factory-6607cd31.tgz</a><br>
+ MD5: f78ac6ba8bb84038d163cc2d7ca85040<br>
+ SHA-1: 6607cd31858af1bfd50b905c68f7cf1f0b6e570e
</td>
</tr>
<tr id="sturgeon-non-preview">
<td>
Non-preview image (for after testing)
</td>
- <td><a href="#top" onclick="onDownload(this)">sturgeon-mec23l-factory-48003078.tgz</a><br>
- MD5: 417b5cbddb29a2262bce133e283d2732<br>
- SHA-1: 4800307843580f818557dd7c43d8ba2161e289b2
+ <td><a href="#top" onclick="onDownload(this)">sturgeon-m6e69f-factory-e659286a.tgz</a><br>
+ MD5: 12ce6cb0b0e43b67ea46a886eae052ae<br>
+ SHA-1: e659286aa9004f4555a476ede4e8b690f56cfefd
</td>
</tr>
</table>
@@ -337,7 +346,8 @@
</p>
<p class="warning">
- <strong>Warning:</strong> Installing a system image on a watch removes all data from the
+ <strong>Warning:</strong> Installing a system image on a watch
+ removes all data from the
watch, so you should back up your data first.
</p>
@@ -346,8 +356,7 @@
</h4>
<p>
- From the phone, unpair ("Forget") the watch.
- Then on the watch, enable the Developer Options menu and ADB debugging as
+ On the watch, enable the Developer Options menu and ADB debugging as
follows:
</p>
@@ -356,14 +365,14 @@
</li>
<li>Scroll to the bottom of the menu. If no <strong>Developer
- Options</strong> item is provided, tap <strong>About</strong>.
+ Options</strong> item is provided, tap <strong>System</strong>
+ and then <strong>About</strong>.
</li>
<li>Tap the build number 7 times.
</li>
- <li>From the Settings menu, tap the <strong>Developer Options</strong>
- item.
+ <li>From the Settings menu, tap <strong>Developer Options</strong>.
</li>
<li>Enable ADB debugging.
@@ -409,7 +418,9 @@
</li>
<li>Use the following <a href="{@docRoot}tools/help/adb.html">adb
- command</a> to confirm that the watch is available for flashing:
+ command</a> to confirm that the watch is recognized.
+ You may need to turn ADB debugging off and then on for the watch to
+ be recognized:
<code>adb devices</code>
</li>
@@ -423,11 +434,11 @@
devices, <code>fastboot oem unlock</code>
</li>
- <li>On the watch, select the <strong>Unlock</strong> option.
+ <li>On the watch, select the option to unlock the bootloader.
</li>
- <li>Navigate to the directory where you unzipped the system image in Step
- 1. At the top level of that directory,
+ <li>On your computer, navigate to the directory where you unzipped the
+ system image in Step 1. At the top level of that directory,
execute the <code>flash-all</code> script by typing
<code>flash-all.sh</code> or, in the case of Windows,
<code>flash-all.bat</code>. The following may need to
@@ -437,18 +448,19 @@
<h4 id="set_up_watch">
- Set up the watch and begin testing
+ Set up the watch
</h4>
- <p>
- After the <code>flash-all</code> script finishes, your watch reboots.
- Pair the watch with a phone or tablet. The preview now is available
- for testing on the watch. Before installing an app, perform the
- following steps on the watch to re-secure the watch's bootloader:
+ <p>
+ After the <code>flash-all</code> script finishes, the watch reboots.
+ Only pair the watch with a phone (so you can begin testing the preview)
+ by using the instructions in <a href="#set_up_a_phone">Set Up a Phone</a>.
+ Additionally, before installing an app, perform the
+ following steps on the watch to re-secure the watch's bootloader:
</p>
<ol>
- <li>Open the Settings menu (on the watch).
+ <li>Open the Settings menu by long-pressing the physical button.
</li>
<li>Scroll to the bottom of the menu and tap <strong>About</strong>.
@@ -457,15 +469,16 @@
<li>Tap the build number 7 times.
</li>
- <li>From the Settings menu, tap the <strong>Developer Options</strong>
- item.
+ <li>From the Settings menu, tap <strong>Developer Options</strong>.
</li>
<li>Enable ADB debugging.
</li>
<li>Connect the watch to your computer and tap <strong>Always allow from
- this computer</strong>.
+ this computer</strong>. (You may need to turn ADB debugging off
+ and then on, to be prompted to always allow ADB debugging from
+ the connected computer.)
</li>
<li>Use the following adb command to start the device in fastboot mode:
@@ -477,13 +490,18 @@
devices, <code>fastboot oem lock</code>
</li>
- <li>On the watch, continue the boot by choosing
- <strong>Start</strong> and touching <strong>'0'</strong>.
+ <li>On the watch, continue the boot as follows:
+ On an LGE Watch Urbane 2nd Edition, choose
+ <strong>Start</strong> and touch <strong>'0'</strong>.
+ On a Huawei Watch, confirm that <strong>Reboot</strong> is chosen and
+ long-press the physical button.
</li>
</ol>
<p>
- Your watch is ready for you to <a href=
+ After you follow the instructions in
+ <a href="#set_up_a_phone">Set Up a Phone</a>,
+ your watch will be ready for you to <a href=
"{@docRoot}training/wearables/apps/creating.html#Install">install and run
your app</a>:
</p>
@@ -539,13 +557,116 @@
device reset and removes all user data on the device.
</p>
+ <h2 id="set_up_a_phone">
+ Set Up a Phone
+ </h2>
+
+ <p>
+ On a phone, follow the instructions in this section to install the beta
+ version of the Android Wear companion app. The beta version cannot be run
+ on a phone at the same time as the non-beta version. Additionally, the
+ beta version is English-only.
+ </p>
+
+ <p>
+ <p class="caution"><strong>Caution:</strong> If you have an existing
+ pairing of the phone to a Wear 1.x
+ watch, installation of the beta companion app will cause a loss of that
+ pairing.
+ </p>
+
+ <h3 id="join-the-wear-2-0-preview-group">
+ Join the Wear 2.0 preview group
+ </h3>
+
+ <p>
+ To access the beta companion app, you must <a href=
+ "https://groups.google.com/forum/#!forum/android-wear-developer-preview">join
+ the preview group in Google Groups</a>.
+ </p>
+
+ <h3>
+ Opt in for beta testing
+ </h3>
+
+ <p>
+ On the <a href=
+ "https://play.google.com/apps/testing/com.google.android.wearable.app">Testing
+ Opt-in</a> page, select <strong>Become a Tester</strong>.
+ </p>
+
+ <h3 id="download-and-install-the-beta-version-of-the-companion-app">
+ Download and install the beta version of the companion app
+ </h3>
+
+ <p>
+ On the Play Store on your phone, go to the <a href=
+ "https://play.google.com/store/apps/details?id=com.google.android.wearable.app">
+ Android Wear app listing</a>. Tap <strong>Update</strong> to download and
+ install the beta version of the app. After installation, confirm that
+ <strong>Auto-update</strong> is selected for the app (see
+ the "Set up automatic updates for specific apps" section of <a href=
+ "https://support.google.com/googleplay/answer/113412">Update downloaded
+ apps</a>). Tap <strong>Open</strong> to start the app.
+ </p>
+
+ <h3 id="pairing">
+ Pair the phone to the watch
+ </h3>
+
+ <p>
+ After you install the beta version of the companion app on a phone,
+ unpair ("Forget") any obsolete watch pairings, if necessary.
+ Then you can pair the phone to a newly-imaged watch:
+ </p>
+
+ <ol>
+ <li>On the phone, select your device name from the list of devices.
+ A pairing code is displayed on the phone and on the watch.
+ Ensure that the codes match.
+ </li>
+
+ <li>Tap <strong>Pair</strong> to
+ continue the pairing process. When the watch is connected to
+ the phone, a confirmation message is displayed.
+ On the phone, a screen is displayed that lists
+ the accounts on the phone.
+ </li>
+
+ <li>Choose a Google Account to add and sync to your watch.
+ </li>
+
+ <li>Confirm the screen lock and enter the password to start the copying of
+ the account from the phone to the watch.
+ </li>
+
+ <li>Follow the instructions in the wizard to finish the
+ pairing process.
+ </li>
+ </ol>
+
+ <p>
+ You can begin testing your app with the preview.
+ </p>
+
<h2 id="set_up_an_emulator">
Set Up an Emulator
</h2>
<p>
- To test with the Android Emulator, create a virtual device in Android
- Studio as follows:
+ To test with the Android Emulator,
+ confirm that you have the latest version of the <strong>Android SDK
+ Platform-tools</strong> from the <a href=
+ "{@docRoot}studio/intro/update.html#sdk-manager">SDK Manager</a>.
+ </p>
+
+ <p>
+ After you create a virtual device as described below, follow the steps for
+ <a href="#set_up_a_phone">setting up a phone</a> with the beta version of
+ the Android Wear companion app.
+ </p>
+
+ <p>Create a new virtual device in Android Studio as follows:
</p>
<ol>
@@ -556,19 +677,19 @@
<li>Click <strong>Create Virtual Device</strong>.
</li>
- <li>In the <strong>Category</strong> pane, select Wear and
- choose a hardware profile.
+ <li>In the <strong>Category</strong> pane, select <strong>Wear</strong>
+ and choose a hardware profile.
The Android Wear 2.0 Developer Preview
is only optimized for round devices currently, so we recommend not
using the square or chin profiles for now.
Click <strong>Next</strong>.
</li>
- <li>Select an <strong>N</strong> image to download. The images may be on
+ <li>Select a <strong>Nougat</strong> image to download. The images may be on
the <strong>x86</strong> tab instead of the <strong>Recommended</strong>
tab, until installed. For example, select the image with the
- <strong>Release Name</strong> of N, the <strong>API Level</strong> of N,
- and the <strong>Target</strong> of "Android 6.X (with Android Wear)".
+ <strong>Release Name</strong> of Nougat, the <strong>API Level</strong> of 24,
+ and the <strong>Target</strong> of "Android 7.0 (with Android Wear)".
When the download and installation are complete, click
<strong>Finish</strong> and then click <strong>Next</strong>.
</li>
@@ -576,16 +697,68 @@
<li>Verify the configuration of the Android Virtual Device (AVD) and
click <strong>Finish</strong>.
</li>
+
+ <li>Start the emulator by selecting the new virtual device, clicking the
+ <strong>Play</strong> button, and waiting until
+ the emulator initializes and shows the Android Wear home screen.
+ </li>
</ol>
<p>
- You can now test an application with a virtual preview device
+ Pair the phone with the emulator, and sync a Google Account, as follows:
+ </p>
+
+ <ol>
+ <li>Follow the steps for
+ <a href="#set_up_a_phone">setting up a phone</a> with the beta version of
+ the Android Wear companion app.
+ </li>
+
+ <li>On the phone, enable Developer Options and USB Debugging.
+ </li>
+
+ <li>Connect the phone to your computer through USB.
+ </li>
+
+ <li>Forward the AVD's communication port to the connected handheld device
+ (each time the phone is connected):<br>
+ <code>adb -d forward tcp:5601 tcp:5601</code>
+ </li>
+
+ <li>On the phone, in the Android Wear app, begin the standard pairing
+ process. For example, on the Welcome screen, tap the
+ <strong>Set It Up</strong> button.
+ Alternatively, if an existing watch already is paired, in the upper-left
+ drop-down, tap <strong>Add a New Watch</strong>.
+ </li>
+
+ <li>On the phone, in the Android Wear app, tap the
+ Overflow button, and then tap
+ <strong>Pair with Emulator</strong>.
+ </li>
+
+ <li>Tap the Settings icon.
+ </li>
+
+ <li>Under Device Settings, tap <strong>Emulator</strong>.
+ </li>
+
+ <li>Tap <strong>Accounts</strong> and select a Google Account,
+ and follow the steps in the wizard to
+ sync the account with the emulator. If necessary, type the screen-lock
+ device password, and Google Account password, to start the account sync.
+ </li>
+ </ol>
+
+ <p>
+ You can now test an app with a virtual preview device
in the <a href=
"{@docRoot}tools/devices/emulator.html">Android Emulator</a>. For more
information about using virtual devices, see <a href=
- "{@docRoot}tools/devices/managing-avds.html">Managing AVDs with the AVD
- Manager</a>.
+ "{@docRoot}studio/run/managing-avds.html">
+ Create and Manage Virtual Devices</a>.
</p>
+
</div><!-- landing -->
</div><!-- relative wrapper -->
diff --git a/docs/html/wear/preview/features/app-distribution.jd b/docs/html/wear/preview/features/app-distribution.jd
new file mode 100644
index 0000000..afc9516
--- /dev/null
+++ b/docs/html/wear/preview/features/app-distribution.jd
@@ -0,0 +1,329 @@
+page.title=App Distribution
+meta.keywords="wear-preview"
+page.tags="wear-preview"
+page.image=images/cards/card-n-sdk_2x.png
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+
+ <ul>
+ <li><a href="#publish">Publish Your APKs</a></li>
+ <li><a href="#targeting">Setting Up Targeting for a Watch</a></li>
+ <li><a href="#console">Using the Play Developer Console</a></li>
+ </ul>
+
+</div>
+</div>
+
+ <p>
+ With Android Wear 2.0, a user can visit the Play Store on a watch and
+ download a Wear app directly to the watch.
+ </p>
+
+ <p>
+ Generally, a Wear 2.0 app in the Play Store needs
+ a minimum and target API level of 24 or higher in
+ the Android manifest file. The minimum SDK level can be 23
+ only if you are using the same APK
+ for Wear 1.0 and 2.0 (and thus have an embedded Wear 1.0 APK).
+ </p>
+
+ <h2 id="publish">
+ Publish Your APKs
+ </h2>
+
+ <p>
+ To make your app appear in the on-watch Play Store, upload
+ the watch APK in the Play Developer Console just as you would any other
+ APK. If you only have a watch APK and no phone APK, no other steps
+ are required.
+ </p>
+
+ <p>
+ If you have a phone APK in addition to a watch APK, you must use the
+ <a href="https://developer.android.com/google/play/publishing/multiple-apks.html">Multi-APK delivery method</a>.
+ </p>
+
+ <p>
+ <a href=
+ "https://developer.android.com/training/permissions/requesting.html">Run-time
+ permissions</a> are required.
+ </p>
+
+ <p>
+ Also see
+ <a href="{@docRoot}wear/preview/features/standalone-apps.html">
+ Standalone Apps</a>.
+ </p>
+
+ <h3>
+ Distribution to Wear 2.0 watches
+ </h3>
+
+ <p>
+ If you only want your app to be distributed to Wear 2.0 watches,
+ it is unnecessary to embed the watch APK inside the the phone APK.
+ </p>
+
+ <p>
+ If you want your app to
+ be distributed to Wear 1.0 watches, you need to embed the
+ watch APK inside the phone APK, as described directly below.
+ </p>
+
+ <h3>
+ Distribution to Wear 1.0 and 2.0 watches
+ </h3>
+
+ <p>
+ If you are already distributing your app to Wear 1.0 watches,
+ follow these steps:
+ </p>
+
+ <ol>
+ <li>Provide a Wear 2.0 (standalone) version of your watch APK that can be made
+ available in the Play Store on Wear.
+ </li>
+
+ <li>Continue embedding a Wear 1.0 APK in your phone APK,
+ for use by watches that do not have Wear 2.0.
+ </li>
+ </ol>
+
+ <h3>
+ Specifying a version code
+ </h3>
+
+ <p>
+ To ensure that a standalone APK acts as an upgrade to an embedded Wear APK, the
+ standalone Wear APK's <a href=
+ "https://developer.android.com/google/play/publishing/multiple-apks.html#VersionCodes">
+ version code</a> generally should be higher than the embedded Wear APK's version code.
+ (A phone APK's version code scheme can be independent from that of a watch
+ APK, although they must be unique.) However, the version codes
+ of the standalone APK and the embedded Wear APK can be the same if
+ the APKs are equivalent. If the APKs are not equivalent,
+ but the version code is the same, then when a watch updates from Wear 1.0
+ to 2.0, the watch may get the new APK only after waiting for a
+ longer-than-expected period of time.
+ </p>
+
+ <p>
+ Note that it currently is not possible to create a single APK that works
+ on a phone and watch.
+ </p>
+
+ <h3>
+ Support in the Gradle file
+ </h3>
+
+ <p>
+ If you have a Wear app that is intended for both Wear 1.0 and Wear 2.0,
+ consider using <a href=
+ "https://developer.android.com/studio/build/build-variants.html#product-flavors">
+ product flavors</a>. For example,
+ if you want to target both SDK version 23 and version 24,
+ update your Wear module's <code>build.gradle</code> file to include
+ the following if an existing embedded app has a minimum SDK version of 23:
+ </p>
+
+<pre>
+android {
+ // Allows you to reference product flavors in your
+ // phone module's build.gradle file
+ publishNonDefault true
+ ...
+ defaultConfig
+ {
+ // This is the minSdkVersion of the Wear 1.0 embedded app
+ minSdkVersion 23
+ ...
+ }
+ buildTypes {...}
+ productFlavors {
+ wear1 {
+ // Use the defaultConfig value
+ }
+ wear2 {
+ minSdkVersion 24
+ }
+ }
+}
+</pre>
+
+ <p>
+ Then update your phone module’s <code>build.gradle</code> file, replacing
+ <code>wearApp</code> as follows:
+ </p>
+
+<pre>
+dependencies {
+ ...
+ wearApp project(path: ':wear', configuration: 'wear1Release')
+}
+</pre>
+
+ <p>
+ A <a href=
+ "https://developer.android.com/studio/build/build-variants.html#product-flavors">
+ build variant</a> is a combination of the product flavor and build type.
+ In Android Studio, select the appropriate build variant when
+ debugging or publishing your app. For example, if <code>wear2</code> is a
+ product flavor, select <strong>wear2Release</strong> as the
+ release build variant.
+ </p>
+
+ <p>
+ For purposes of code that is Wear 2.0-specific or Wear 1.0-specific,
+ consider <a href=
+ "https://developer.android.com/studio/build/build-variants.html#sourcesets">
+ source sets for build variants</a>.
+ </p>
+
+
+ <h2 id="targeting">
+ Setting Up Targeting for a Watch
+ </h2>
+
+ <p>
+ In your Android Manifest file, you must specify the following feature
+ restriction: the <code>uses-feature</code> element is set to
+ <code>android.hardware.type.watch</code>. Do not set
+ the <code>required</code> attribute to <code>false</code>.
+ A single APK for Wear and non-Wear devices presently is not supported.
+ </p>
+
+ <p>
+ Thus, if an APK has the following setting, Google Play provides the APK
+ to watches only:
+ </p>
+
+<pre>
+<manifest package="com.example.standalone"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-feature
+ android:name="android.hardware.type.watch"
+ ...
+</manifest>
+</pre>
+
+ <p>
+ The <code>android.hardware.type.watch</code> setting above can be
+ combined with other criteria such as SDK version, screen resolution, and
+ CPU architecture. Thus, different Wear APKs can target different hardware
+ configurations.
+ </p>
+
+ <h2 id="console">
+ Using the Play Developer Console
+ </h2>
+
+ <p>
+ Below is an introduction to <a href=
+ "https://support.google.com/googleplay/android-developer/answer/113469">uploading</a>
+ a standalone Wear APK to an application listing using the Play Developer
+ Console.
+ </p>
+
+ <p>
+ If your app supports both Wear 1.0 and Wear 2.0, continue embedding the
+ Wear 1.0 APK (minimum SDK version of 20, 21, or 22, or 23) in the phone
+ APK and upload the phone APK. In addition, upload your standalone Wear
+ 2.0 APK (which has a minimum SDK version of 24).
+ </p>
+
+ <p>
+ Also see <a href=
+ "https://developer.android.com/google/play/publishing/multiple-apks.html">
+ Multiple APK Support</a> and <a href=
+ "https://developer.android.com/distribute/googleplay/developer-console.html#manage">
+ Manage Your App</a>.
+ Before uploading an APK as described below, the APK
+ must be <a href=
+ "https://developer.android.com/studio/publish/app-signing.html#release-mode">
+ signed</a>.
+ </p>
+
+ <h3 id="uploading-apk">
+ Uploading your APK
+ </h3>
+
+ <p>
+ Go to the <a href="https://play.google.com/apps/publish">Play Developer
+ Console</a>, navigate to your application listing, and select
+ <strong>APK</strong> in the left-navigation panel. An APK screen similar to
+ the following is displayed:
+ </p>
+ <img src="../images/apk-tabs.png" width="" alt="alt_text">
+
+ <p>
+ You may need to use advanced mode for uploads, as follows:
+ </p>
+
+ <ul>
+ <li>Advanced mode is unnecessary if you only have a Wear 2.0 app and no
+ phone app. Instead of advanced mode, use simple mode.</li>
+
+ <li>Use advanced mode if you support Wear 1.0 or have a phone app.</li>
+ </ul>
+
+ <p>
+ Therefore, on the above APK screen, to determine whether to click
+ the <strong>Switch to advanced mode</strong>
+ button, consider the following:
+ </p>
+
+ <ul>
+ <li>If your app does not support Wear 1.0, and only has a watch APK,
+ upload it using simple mode.
+ </li>
+
+ <li>If your app does not support Wear 1.0 and has both a watch APK and a
+ phone APK, click <strong>Switch to advanced mode</strong>
+ to upload the watch and phone APKs.
+ </li>
+ </ul>
+
+ <p>
+ See <a href=
+ "https://developer.android.com/google/play/publishing/multiple-apks.html#SimpleAndAdvanced">
+ Simple mode and advanced mode</a> for more information about toggling
+ between modes.
+ </p>
+
+ <p>
+ Select the appropriate tab (<strong>Production</strong>, <strong>Beta
+ Testing</strong>, or <strong>Alpha Testing</strong>) for your upload.
+ Then click
+ the <strong>Upload New APK</strong> button and select your standalone
+ Wear APK for upload.
+ </p>
+
+ <h3>
+ Reviewing and publishing
+ </h3>
+
+ <p>
+ After you upload your standalone Wear APK and scroll down the resulting
+ page, the APK is shown in the <strong>Current APK</strong> table, with a
+ version number, in a similar way to the following:
+ </p>
+ <img src="../images/current-apk.png" width="" alt="alt_text">
+
+ <p>
+ Finally, in the <strong>Current APK</strong> table above, click the line
+ with the <strong>Version</strong> to review the APK. The <strong>APK
+ Details</strong> panel is displayed. You can verify, for example, that
+ the number in the <strong>Supported Android Devices</strong> line is far
+ fewer than the number would be for a typical phone APK:
+ </p>
+ <img src="../images/apk-details.png" width="" alt="alt_text">
+
+ <p>
+ When you are ready, <a href=
+ "https://support.google.com/googleplay/android-developer/answer/6334282">publish</a>
+ your app.
+ </p>
diff --git a/docs/html/wear/preview/features/bridger.jd b/docs/html/wear/preview/features/bridger.jd
index b7be093..2d879ca 100644
--- a/docs/html/wear/preview/features/bridger.jd
+++ b/docs/html/wear/preview/features/bridger.jd
@@ -6,19 +6,26 @@
<div id="qv-wrapper">
<div id="qv">
- <ol>
+ <ul>
<li>
<a href=
- "#preventing_bridging_with_the_bridging_mode_feature">Preventing
- Bridging with the Bridging Mode Feature</a>
+ "#using-an-entry-in-the-manifest-file">Specifying a Bridging Configuration in the Manifest File</a>
</li>
<li>
<a href=
- "#using_a_dismissal_id_to_sync_notification_dismissals">Using a
- Dismissal ID to Sync Notification Dismissals</a>
+ "#specifying-a-bridging-configuration-at-runtime">Specifying a Bridging Configuration at Runtime</a>
</li>
- </ol>
+ <li>
+ <a href=
+ "#existing-method-of-preventing-bridging">Existing Method of Preventing Bridging</a>
+ </li>
+
+ <li>
+ <a href=
+ "#using_a_dismissal_id_to_sync_notification_dismissals">Using a Dismissal ID to Sync Notification Dismissals</a>
+ </li>
+ </ul>
</div>
</div>
@@ -27,19 +34,20 @@
"{@docRoot}training/wearables/notifications/index.html">are bridged
(shared)</a> from an app on a companion phone to the watch. If you build
a standalone watch app and have a companion phone app, they may duplicate
- notifications. The Android Wear 2.0 Preview includes a Bridging mode
- feature to handle this problem of repeated notifications.
+ notifications. The Android Wear 2.0 Preview includes
+ features to handle this problem of repeated notifications.
</p>
<p>
- With the Android Wear 2.0 Preview, developers can change the
- behavior of notifications with the following:
+ With the Android Wear 2.0 Preview, developers can change the behavior of
+ notifications with one or more of the following:
</p>
<ul>
- <li>Specifying in the standalone app's Android manifest file that
- notifications from the corresponding phone app should not be
- bridged to the watch
+ <li>Specifying a bridging configuration in the manifest file
+ </li>
+
+ <li>Specifying a bridging configuration at runtime
</li>
<li>Setting a dismissal ID so notification dismissals are synced across
@@ -47,43 +55,201 @@
</li>
</ul>
- <h2 id="preventing_bridging_with_the_bridging_mode_feature">
- Preventing Bridging with the Bridging Mode Feature
+ <h2 id="using-an-entry-in-the-manifest-file">
+ Specifying a Bridging Configuration in the Manifest File
</h2>
<p>
- To prevent bridging of notifications from a phone app, you can use an
+ An app's Android manifest file can indicate that notifications from the
+ corresponding phone app should not be bridged to the watch. Specifically,
+ to prevent bridging of notifications from a phone app, you can use a
+ <code><meta-data></code>
entry in the manifest file of the watch app (e.g. the standalone watch
app), as follows:
</p>
- <pre>
+<pre>
com.google.android.wearable.notificationBridgeMode
- </pre>
+</pre>
<p>
Setting that entry to <code>NO_BRIDGING</code> will prevent bridging:
</p>
- <pre>
-<meta-data android:name="com.google.android.wearable.notificationBridgeMode"
- android:value="NO_BRIDGING" />
+<pre>
+<meta-data android:name="com.google.android.wearable.notificationBridgeMode"
+ android:value="NO_BRIDGING" />
</pre>
+
<p>
- The default bridging behavior occurs if you do not include the entry or
+ The default bridging behavior occurs if you do not
+ include the <code><meta-data></code> entry or
if you specify a value of <code>BRIDGING</code> instead of
<code>NO_BRIDGING</code>.
</p>
- <h3 id="existing_method_of_preventing_bridging">
- Existing method of preventing bridging
+ <p>
+ For an existing app, if you are using
+ Google Cloud Messaging (GCM) or Firebase Cloud
+ Messaging (FCM) to send notification alerts to devices,
+ you may already have disabled bridging in case a phone is not
+ connected at the time of receiving an alert.
+ In this case, you may still want to dismiss the notification
+ across other devices when it is dismissed in a watch app.
+ </p>
+
+ <p>
+ The bridging configuration that is set in the manifest takes effect as
+ soon as a watch app is installed.
+ </p>
+
+ <h2 id="specifying-a-bridging-configuration-at-runtime">
+ Specifying a Bridging Configuration at Runtime
+ </h2>
+
+ <p>
+ This section describes how to specify a bridging configuration at runtime
+ using the <code>BridgingManager</code> class
+ <code>(android.support.wearable.notifications.BridgingManager)</code>.
+ </p>
+
+ <p>
+ You can set a bridging mode, and optionally set tags for notifications
+ that are exempt from the bridging mode, using a
+ <code>BridgingManager</code> object. Specifically, create a
+ <code>BridgingConfig</code> object and set it as shown in this section,
+ optionally using the <code>setBridgingEnabled</code> method. If you
+ specify a bridging configuration at runtime, then if the
+ <code>setBridgingEnabled</code> method is not set, bridging is enabled by
+ default.
+ </p>
+
+ <p>
+ Specifying a bridging configuration at runtime overrides a
+ bridging-related setting in the Android manifest file.
+ </p>
+
+ <h3 id="disable-bridging-for-all-notifications">
+ Disable bridging for all notifications
</h3>
<p>
+ You can use the <code>setBridgingEnabled</code> method, as follows:
+ </p>
+
+<pre>
+BridgingManager.setConfig(context,
+ new BridgingConfig.Builder(context)
+ .setBridgingEnabled(false)
+ .build());
+</pre>
+ <p>
+ If the above setter is not called, the bridging mode defaults to true.
+ Here is an example of setting tags without using the
+ <code>setBridgingEnabled</code> method, excluding notifications with a
+ tag of <code>foo</code> or <code>bar</code>:
+ </p>
+
+<pre>
+BridgingManager.setConfig(context,
+ new BridgingConfig.Builder(context)
+ .addExcludedTag("foo")
+ .addExcludedTag("bar")
+ .build());
+</pre>
+ <h3 id="exempt-notifications-that-are-tagged">
+ Exempt notifications that are tagged
+ </h3>
+
+ <p>
+ You can disable bridging for all notifications except those with certain
+ tags.
+ </p>
+
+ <p>
+ For example, you can disable bridging, except for notifications tagged as
+ <code>foo</code> or <code>bar,</code> with the following:
+ </p>
+
+<pre>
+BridgingManager.setConfig(context,
+ new BridgingConfig.Builder(context)
+ .setBridgingEnabled(false)
+ .addExcludedTag("foo")
+ .addExcludedTag("bar")
+ .build());
+</pre>
+
+ <p>
+ As another example, you can disable bridging for all notifications except
+ for notifications tagged as <code>foo</code>, <code>bar</code> or
+ <code>baz</code>.
+ </p>
+
+ <pre>
+BridgingManager.setConfig(context,
+ new BridgingConfig.Builder(context)
+ .setBridgingEnabled(false)
+ .addExcludedTags(Arrays.asList("foo", "bar", "baz"))
+ .build());
+</pre>
+ <h3 id="enable-bridging-except-for-notifications-with-certain-tags">
+ Enable bridging except for notifications with certain tags
+ </h3>
+
+ <p>
+ You can enable bridging for all notifications except those with certain
+ tags.
+ </p>
+
+ <p>
+ For example, you can enable bridging for all notifications, except for
+ notifications tagged as <code>foo</code> or <code>bar</code>, with the
+ following:
+ </p>
+
+<pre>
+BridgingManager.setConfig(context,
+ new BridgingConfig.Builder(context)
+ .setBridgingEnabled(true)
+ .addExcludedTag("foo")
+ .addExcludedTag("bar")
+ .build());
+</pre>
+
+ <h3 id="setting-a-bridge-tag">
+ Setting a bridge tag
+ </h3>
+
+ <p>
+ A bridge tag can be set on a notification by calling the
+ <code>setNotificationBridgeTag</code> method as follows:
+ </p>
+
+<pre>
+BridgingManager.setNotificationBridgeTag(<NotificationCompat.Builder>, <String>);
+</pre>
+
+ <p>
+ For example:
+ </p>
+
+<pre>
+NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
+<set other fields>;
+BridgingManager.setNotificationBridgeTag(builder, "foo");
+Notification notification = builder.build();
+</pre>
+
+ <h2 id="existing-method-of-preventing-bridging">
+ Existing Method of Preventing Bridging
+ </h2>
+
+ <p>
An existing way to prevent bridging is with the
<code>Notification.Builder</code> class; specify <code>true</code> in the
<a href=
- "{@docRoot}reference/android/app/Notification.Builder.html#setLocalOnly(boolean)">
+ "http://developer.android.com/reference/android/app/Notification.Builder.html#setLocalOnly(boolean)">
setLocalOnly</a> method.
</p>
@@ -95,12 +261,6 @@
the watch app may not be installed on all of them.
</p>
- <p>
- Thus, if bridging should be prevented when the watch app
- is installed, use the <a href=
- "#preventing_bridging_with_the_bridging_mode_feature">Bridging mode
- feature</a>.
- </p>
<h2 id="using_a_dismissal_id_to_sync_notification_dismissals">
Using a Dismissal ID to Sync Notification Dismissals
@@ -110,7 +270,7 @@
If you prevent bridging with the Bridging mode feature, dismissals
(cancellations) of notifications are not synced across a user's devices.
However, the following methods of the <a href=
- "{@docRoot}reference/android/support/v4/app/NotificationCompat.WearableExtender.html">
+ "http://developer.android.com/reference/android/support/v4/app/NotificationCompat.WearableExtender.html">
NotificationCompat.WearableExtender</a> class enable you to use dismissal
IDs:
</p>
@@ -118,7 +278,7 @@
<pre>
public WearableExtender setDismissalId(String dismissalId)
public String getDismissalId()
- </pre>
+</pre>
<p>
To enable a dismissal to be synced, use the <code>setDismissalId()</code>
method. For each notification, pass a globally unique ID, as a string,
@@ -135,12 +295,12 @@
<pre>
NotificationCompat.WearableExtender wearableExtender =
-new NotificationCompat.WearableExtender().setDismissalId(“abc123”);
+new NotificationCompat.WearableExtender().setDismissalId("abc123");
Notification notification = new NotificationCompat.Builder(context)
<set other fields>
.extend(wearableExtender)
.build();
- </pre>
+</pre>
<p>
Dismissal IDs work if a watch is paired to an Android phone, but not if a
watch is paired to an iPhone.
diff --git a/docs/html/wear/preview/features/complications.jd b/docs/html/wear/preview/features/complications.jd
index 3334cb7..c866118 100644
--- a/docs/html/wear/preview/features/complications.jd
+++ b/docs/html/wear/preview/features/complications.jd
@@ -13,6 +13,13 @@
Complications to a Watch Face</a>
</li>
<li>
+ <a href="#permissions-for-complication-data">Permissions
+ for Complication Data</a>
+ </li>
+ <li>
+ <a href="#default-providers">Default Providers for Watch Faces</a>
+ </li>
+ <li>
<a href="#exposing_data_to_complications">Exposing Data to
Complications</a>
</li>
@@ -27,12 +34,14 @@
<a href="#api_additions">API Additions</a>
</li>
</ol>
+
<h2>See Also</h2>
<ol>
<li><a class="external-link"
href="https://github.com/googlesamples/android-WatchFace">Watch
Face sample app with complications</a></li>
</ol>
+
</div>
</div>
@@ -56,9 +65,12 @@
</p>
<p>
- Along with reviewing this page, download the Android Wear 2.0 Preview
- Reference (see the Complications API <a href=
- "#api_additions">additions</a>) and review the Javadoc for complications.
+ You can review the Javadoc for complications by downloading
+ the Android Wear 2.0 Preview
+ Reference. Also see the <a href="#api_additions">API additions for
+ complications</a> and the
+ <a href="https://developer.android.com/wear/preview/behavior-changes.html">
+ behavior changes</a> for Wear 2.0.
</p>
<p>
@@ -117,8 +129,8 @@
<code>WatchFaceService.Engine</code> class, with a list of watch face
complication IDs. A watch face creates these IDs to uniquely identify
slots on the watch face where complications can appear, and passes them
- to the <code>createProviderChooserIntent</code> method (of the
- <code>ProviderChooserIntent</code> class) to allow the user to decide
+ to the <code>createProviderChooserIntent</code> method
+ to allow the user to decide
which complication should go in which slot.
</p>
@@ -186,6 +198,406 @@
where possible.
</p>
+ <h2 id="permissions-for-complication-data">
+ Permissions for Complication Data
+ </h2>
+
+ <p>
+ A watch face must have the following <a href=
+ "https://developer.android.com/training/permissions/requesting.html">permission</a>
+ to receive complication data and open the provider chooser:
+ </p>
+
+<pre>
+com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA
+</pre>
+
+ <h3 id="opening-the-provider-chooser">
+ Opening the provider chooser
+ </h3>
+
+ <p>
+ A watch face that was not granted the above permission will be unable to
+ start the provider chooser.
+ </p>
+
+ <p>
+ To make it easier to request the permission and start the chooser, the
+ <code>ComplicationHelperActivity</code> class is available in the
+ wearable support library. This class should be used instead of
+ <code>ProviderChooserIntent</code> to start the chooser in almost all
+ cases.
+ </p>
+
+ <h4 id="requesting-the-necessary-permission">
+ Requesting the necessary permission
+ </h4>
+
+ <p>
+ To use <code>ComplicationHelperActivity</code>, add it to the watch face
+ in the <a href=
+ "https://developer.android.com/guide/topics/manifest/manifest-intro.html">
+ manifest file</a>:
+ </p>
+
+<pre>
+<activity android:name="android.support.wearable.complications.ComplicationHelperActivity"/>
+</pre>
+
+ <p>
+ To start the provider chooser, call the
+ <code>ComplicationHelperActivity.createProviderChooserHelperIntent</code>
+ method, to obtain an intent.
+ </p>
+
+ <p>
+ The new intent can be used with either <code>startActivity</code> or
+ <code>startActivityForResult</code> to launch the chooser.
+ </p>
+
+ <p>
+ Here is an example of using the new intent with
+ <code>startActivityForResult</code>:
+ </p>
+
+ <pre>
+startActivityForResult(
+ ComplicationHelperActivity.createProviderChooserHelperIntent(
+ getActivity(),
+ watchFace,
+ complicationId,
+ ComplicationData.TYPE_LARGE_IMAGE),
+ PROVIDER_CHOOSER_REQUEST_CODE);
+</pre>
+ <p>
+ When the helper activity is started, the helper activity checks if the
+ permission was granted. If the permission was not granted, the helper
+ activity makes a runtime permission request. If the permission request is
+ accepted (or is unneeded), the provider chooser is shown.
+ </p>
+
+ <p>
+ If <code>startActivityForResult</code> was used with the intent, the
+ result delivered back to the calling Activity will have a result code of
+ <code>RESULT_OK</code> if a provider was successfully set, or a result
+ code of <code>RESULT_CANCELLED</code> if no provider was set.
+ </p>
+
+ <p>
+ In the case where a provider was set,
+ <code>ComplicationProviderInfo</code> for the chosen provider will be
+ included in the data intent of the result, as an extra with the key
+ <code>ProviderChooserIntent#EXTRA_PROVIDER_INFO</code>.
+ </p>
+
+ <h3 id="receiving-complication-data">
+ Receiving complication data
+ </h3>
+
+ <p>
+ In general, watch faces need the above permission in order to receive
+ complication data, but there are some exceptions. Specifically, a watch
+ face can only receive data from a provider if one of the following is
+ true:
+ </p>
+
+ <ul>
+ <li>The provider is a "safe" system provider,
+ </li>
+
+ <li>The provider and watch face are from the same app,
+ </li>
+
+ <li>The provider whitelists the watch face as a "safe" watch face, or
+ </li>
+
+ <li>The watch face has the permission
+ </li>
+ </ul>
+
+ <h4 id="lack-of-appropriate-permission">
+ Lack of appropriate permission
+ </h4>
+
+ <p>
+ If none of the above is true, then when <code>ComplicationData</code>
+ normally would be sent by a provider to a watch face, the system instead
+ sends data of the type <code>TYPE_NO_PERMISSION</code>. This type
+ includes an icon (an exclamation mark) and short text ("--") to allow it
+ to be rendered as if it were of the short text type or icon type, for
+ convenience.
+ </p>
+
+ <p>
+ When a watch face receives data of <code>TYPE_NO_PERMISSION</code>, the
+ watch face should render this appropriately, so the user can see that
+ action is needed for the complication to work. If possible, a tap on a
+ complication in this state should launch a permission request. This can
+ be done using
+ <code>ComplicationHelperActivity.createPermissionRequestHelperIntent</code>,
+ if the helper activity was added to the watch face app.
+ </p>
+
+ <p>
+ If a user accepts the permission request created by the helper activity,
+ updates are requested for all the active complications on the watch face
+ automatically, allowing the <code>TYPE_NO_PERMISSION</code> data to be
+ replaced by real data.
+ </p>
+
+ <h4 id="safe-providers">
+ Safe providers
+ </h4>
+
+ <p>
+ Some system providers are considered "safe", because they only supply
+ information that the watch face already could obtain itself.
+ </p>
+
+ <p>
+ These providers are listed in the new <code>SystemProviders</code> class
+ in the wearable support library. Whether a system provider is safe is
+ stated in the Javadoc (in the Android Wear 2.0 Preview Reference). Also
+ see <a href="#system-providers">System providers</a> for a list.
+ </p>
+
+ <h4 id="provider-specified-safe-watch-faces">
+ Provider-specified safe watch faces
+ </h4>
+
+ <p>
+ Providers can specify certain watch faces as "safe" to receive their
+ data. This is intended to be used only when the watch face will attempt
+ to use the provider as a default (see below),
+ and the provider trusts the watch face app.
+ </p>
+
+ <p>
+ To declare watch faces as safe, the provider adds metadata with a key of
+ <code>android.support.wearable.complications.SAFE_WATCH_FACES</code>. The
+ metadata value should be a comma-separated list (whitespace is ignored).
+ Entries in the list can be component names (of
+ <code>WatchFaceServices</code>, given as if
+ <code>ComponentName.flattenToString()</code> had been called), or they
+ can be package names (of apps, in which case every watch face within a
+ specified app is considered safe).
+ </p>
+
+ <p>
+ For example:
+ </p>
+
+<pre>
+<meta-data
+ android:name="android.support.wearable.complications.SAFE_WATCH_FACES"
+ android:value="
+ com.app.watchface/com.app.watchface.MyWatchFaceService,
+ com.anotherapp.anotherwatchface/com.something.WatchFaceService,
+ com.something.text
+ "/>
+</pre>
+
+ <h2 id="default-providers">
+ Default Providers for Watch Faces
+ </h2>
+
+ <p>
+ Watch faces can specify default providers that are used until a user
+ selects a provider.
+ </p>
+
+ <h3 id="setting-default-providers">
+ Setting default providers
+ </h3>
+
+ <p>
+ Set default providers using the
+ <code>setDefaultComplicationProvider</code> method in
+ <code>WatchFaceService.Engine</code>. This method may be called at any
+ time, but it does nothing if the user already chose a provider for the
+ given complication.
+ </p>
+
+ <h3 id="safe-providers2">
+ Safe providers
+ </h3>
+
+ <p>
+ For most providers, the <code>RECEIVE_COMPLICATION_DATA</code> permission
+ must be granted to a watch face before data can flow to it. However, some
+ system providers are considered "safe", and do not require the watch face
+ to have the permission for data to be sent (see <a href=
+ "#safe-providers">Safe Providers</a> and <a href=
+ "#system-providers">System providers</a>). These providers may be
+ preferable to use as defaults, as they can supply data immediately.
+ </p>
+
+ <p>
+ Alternatively, if a watch face has a partnership with a certain provider
+ and wishes to use it as a default, it can request that the provider list
+ it as a safe watch face (see <a href=
+ "#provider-specified-safe-watch-faces">Provider-specified safe watch
+ faces</a>).
+ </p>
+
+ <h3 id="system-providers">
+ System providers
+ </h3>
+
+ <p>
+ The system includes providers that can be used as defaults. These are
+ listed in the <code>SystemProviders</code> class in the wearable support
+ library.
+ </p>
+
+ <p>
+ The following table has details about providers that are considered safe:
+ </p>
+
+ <table>
+ <tr>
+ <th>
+ Method name in the SystemProviders class
+ </th>
+ <th>
+ Safety
+ </th>
+ <th>
+ Can be the default
+ </th>
+ <th>
+ Notes
+ </th>
+ </tr>
+
+ <tr>
+ <td>
+ <code>dateProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ The standard system date provider. Tapping opens the standard Agenda
+ app.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>currentTimeProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ The standard system "time and date" provider. No tap action.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>batteryProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ The standard system battery provider. No tap action.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>stepCountProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Shows a daily total of steps, as reported by
+ <code>readDailyTotal</code>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>unreadCountProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Shows the number of unread notifications in the stream.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>worldClockProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Will default to London or New York. Can be tapped to change the time
+ zone.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>appsProvider()</code>
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Yes
+ </td>
+ <td>
+ Will show an "apps" icon at first, which can be tapped to choose an
+ app.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>nextEventProvider()</code>
+ </td>
+ <td>
+ No
+ </td>
+ <td>
+ Yes (but not a safe provider)
+ </td>
+ <td>
+ The standard system "next event" provider. Tapping opens
+ the standard Agenda app.
+ </p>
+ </td>
+ </tr>
+ </table>
+
+
<h2 id="exposing_data_to_complications">
Exposing Data to Complications
</h2>
@@ -203,6 +615,11 @@
be used to send data back to the system.
</p>
+ <p class="note"><strong>Note:</strong> When you provide data as a
+ complication data provider, the watch face receives the raw values
+ you send so it can draw them on the watch face.
+ </p>
+
<p>
In your app's manifest, declare the service and add an intent filter for
the following:
@@ -210,7 +627,8 @@
<pre>
android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST
-</pre>
+ </pre>
+
<p>
The service's manifest entry should also include an
<code>android:icon</code> attribute. The provided icon should be a
@@ -227,6 +645,21 @@
<a href="#api_additions">API Additions</a>).
</p>
+ <p>
+ Additionally, a permission for provider services ensures that only the Android Wear system
+ can bind to provider services. Only the Android Wear system can have this
+ permission.
+ </p>
+
+ <p>
+ Provider services should add the following to their service declarations
+ in the manifest:
+ </p>
+
+<pre>
+android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER"
+</pre>
+
<h3 id="update_period">
Update period
</h3>
@@ -371,6 +804,11 @@
<p>
The following table describes the types and fields of the
<code>ComplicationData</code> object.
+ If a watch face requests a field that is invalid for a complication type,
+ a default value for the field is returned.
+ For example, if a watch face tries to access a <code>Long text</code>
+ field in a <code>SHORT_TEXT</code> type, the default value for the
+ <code>Long text</code> field is returned.
</p>
<table>
@@ -489,56 +927,80 @@
</table>
<p>
- In addition, the following two types have no fields. These two types may
- be sent for any complication slot and do not need to be included in a
- list of supported types:
+ In addition, the types in the table below are for empty data and
+ may be sent for any complication slot. These types have no fields
+ and do not need to be included in a
+ list of supported types. These types enable watch
+ faces to differentiate among the following three cases:
+ </p>
+
+ <ul>
+ <li>No provider was chosen
+ </li>
+
+ <li>The user has selected "empty" for a slot
+ </li>
+
+ <li>A provider has no data to send
+ </li>
+ </ul>
+
+ <p>
+ Providers should not send <code>TYPE_EMPTY</code> in response to
+ update requests. Providers should send <code>TYPE_NO_DATA</code> instead.
+ </p>
+
+ <p>
+ Details on the complication types for "empty" data are in the
+ following table:
</p>
<table>
<tr>
- <th style="width:175px">
- Type
+ <th>Complication type
</th>
- <th style="width:175px">
- Required fields
- </th>
- <th style="width:175px">
- Optional fields
- </th>
- <th>
- Notes
+ <th>Description
</th>
</tr>
<tr>
<td>
- NOT_CONFIGURED
+ <code>TYPE_NOT_CONFIGURED</code>
</td>
<td>
- None
- </td>
- <td>
- None
- </td>
- <td>
- Sent when a provider has not yet been chosen for a complication.
+ Sent by the system when a complication is activated but the user has
+ not selected a provider, and no default was set.
+ <p>
+ Cannot be sent by providers.
+ </p>
</td>
</tr>
<tr>
<td>
- EMPTY
+ <code>TYPE_EMPTY</code>
</td>
<td>
- None
+ Sent by the system when a complication is activated and the user has
+ chosen "empty" instead of a provider, or when the watch face has
+ chosen no provider, and this type, as the default.
+ <p>
+ Cannot be sent by providers.
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>TYPE_NO_DATA</code>
</td>
<td>
- None
- </td>
- <td>
- Sent by a provider when there is no data to display in a
- complication, or sent by the system when nothing should be shown in a
- complication.
+ Sent by the system when a complication (that has a provider) is
+ activated, to clear the complication before actual data is received
+ from the provider.
+ <p>
+ Should be sent by providers if they have no actual data to send.
+ </p>
</td>
</tr>
</table>
@@ -700,8 +1162,8 @@
</h2>
<p>
- The Complications API includes new classes in the Wearable Support
- Library. For more information, download the <a href=
+ The Complications API includes new classes in the wearable support
+ library. For more information, download the <a href=
"{@docRoot}wear/preview/start.html#get_the_preview_reference_documentation">
Android Wear 2.0 Preview Reference</a>.
</p>
@@ -722,26 +1184,14 @@
</li>
<li>
- <code>ComplicationText</code>
+ <code>ComplicationHelperActivity</code>
<ul>
- <li>Used to supply text-based values in a
- <code>ComplicationData</code> object
+ <li>Used to request the following permission: <br>
+<code>com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA</code>
</li>
- <li>Includes options for time-dependent values, whose text value
- depends on the current time
- </li>
- </ul>
- </li>
-
- <li>
- <code>ComplicationProviderService</code>
- <ul>
- <li>Extends <code>Service</code> and includes callback methods to
- respond to the complication system
- </li>
-
- <li>Callback methods are all called on the main thread
+ <li>Used instead of <code>ProviderChooserIntent</code>
+ to start the chooser in almost all cases
</li>
</ul>
</li>
@@ -759,13 +1209,35 @@
</li>
<li>
- <code>ProviderChooserIntent</code>
+ <code>ComplicationProviderService</code>
<ul>
- <li>Non-instantiable utility class
+ <li>Extends <code>Service</code> and includes callback methods to
+ respond to the complication system
</li>
- <li>Includes a method that a watch face can call for starting a
- provider chooser (to allow a user to configure complications)
+ <li>Callback methods are all called on the main thread
+ </li>
+ </ul>
+ </li>
+
+ <li>
+ <code>ComplicationText</code>
+ <ul>
+ <li>Used to supply text-based values in a
+ <code>ComplicationData</code> object
+ </li>
+
+ <li>Includes options for time-dependent values, whose text value
+ depends on the current time
+ </li>
+ </ul>
+ </li>
+
+ <li>
+ <code>ProviderChooserIntent</code>
+ <ul>
+ <li>Non-instantiable utility class that is not commonly used; use
+ <code>ComplicationHelperActivity</code> instead
</li>
</ul>
</li>
@@ -789,6 +1261,16 @@
</li>
</ul>
</li>
+
+ <li>
+ <code>SystemProviders</code>
+ <ul>
+ <li>Lists system providers that are considered "safe",
+ because they only supply information that the watch face
+ already could obtain itself
+ </li>
+ </ul>
+ </li>
</ul>
<p>
diff --git a/docs/html/wear/preview/features/notifications.jd b/docs/html/wear/preview/features/notifications.jd
index dcc0970..b546978 100644
--- a/docs/html/wear/preview/features/notifications.jd
+++ b/docs/html/wear/preview/features/notifications.jd
@@ -1,6 +1,5 @@
page.title=Notification Changes in Android Wear 2.0
-meta.tags="wear", "wear-preview", "notifications"
-page.tags="wear"
+meta.tags="wear", "wear-preview", "notifications" page.tags="wear"
page.image=/wear/preview/images/expanded_diagram.png
@@ -12,6 +11,7 @@
<h2>This document includes</h2>
<ol>
<li><a href="#visual">Visual Updates</a></li>
+ <li><a href="#inline">Inline Action</a></li>
<li><a href="#expanded">Expanded Notifications</a></li>
<li><a href="#messaging">MessagingStyle</a></li>
</ol>
@@ -67,7 +67,8 @@
We recommended that you don't set color for bridged notifications.
When Wear apps post local notifications, you can work around this by checking
- <a href="{@docRoot}training/basics/supporting-devices/platforms.html#version-codes">the API level of the device</a> they're running on and using an appropriate color
+ <a href="{@docRoot}training/basics/supporting-devices/platforms.html#version-codes">the API level of the device</a>
+ they're running on and using an appropriate color
for Wear 1.x and a different color for Wear 2.0.
</li>
@@ -77,6 +78,85 @@
you must update the text of your notification.
</li>
</ul>
+
+<h2 id="inline">Inline Action</h3>
+
+<img src="{@docRoot}wear/preview/images/inline_action.png" style="float:right;margin:10px 20px 0 0">
+<p>
+ Wear 2.0 now supports inline action, which allows users to take actions on a
+ notification from within the notification stream card. On Wear, the inline
+ action appears as an additional button displayed at the bottom of the notification.
+</p>
+<p>
+ Inline actions are optional but recommended for cases in which users are likely
+ to take an action on a notification after viewing the contents in the
+ notification stream card (without going to the
+ <a href= "{@docRoot}wear/preview/features/notifications.html#expanded">expanded notification</a>).
+ Examples of good use cases for inline action on a notification include: replying to a
+ text message, stopping a fitness activity, and archiving an email message.
+</p>
+
+<p>
+ A notification can provide only one inline action.
+ To display the inline action as an additional button in the notification, set
+ the <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Action.WearableExtender.html#setHintDisplayActionInline(boolean)">{@code setHintDisplayActionInline()}</a>
+ method to true. When a user taps the inline action, the system invokes
+ the intent that you specified in the notification action.
+</p>
+
+<h3>Adding an inline action</h3>
+<p>
+ The following code example shows how to create a notification with an inline
+ reply action:
+</p>
+
+<ol>
+ <li>Create an instance of
+ <a href="https://developer.android.com/reference/android/support/v4/app/RemoteInput.Builder.html">{@code RemoteInput.Builder}</a></code>
+ that you can add to your notification action. This class's constructor accepts a
+ string that the system uses as the key for the text input. Later, your app
+ uses that key to retrieve the text of the input.
+
+<pre>
+String[] choices = context.getResources().getStringArray(R.array.notification_reply_choices);
+ choices = WearUtil.addEmojisToCannedResponse(choices);
+ RemoteInput remoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT)
+ .setLabel(context.getString(R.string.notification_prompt_reply))
+ .setChoices(choices)
+ .build();
+</pre>
+
+ </li>
+
+ <li>
+ Use the <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Action.Builder.html#addRemoteInput(android.support.v4.app.RemoteInput)">{@code addRemoteInput()}</a>
+ method to attach the <ahref="https://developer.android.com/reference/android/support/v4/app/RemoteInput.html">{@code RemoteInput}</a>
+ object to an action.
+
+<pre>
+NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(
+ R.drawable.ic_full_reply, R.string.notification_reply, replyPendingIntent);
+ actionBuilder.addRemoteInput(remoteInput);
+ actionBuilder.setAllowGeneratedReplies(true);
+</pre>
+ </li>
+
+ <li>
+ Add a hint to display the reply action inline, and use the
+ <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.WearableExtender.html#addAction(android.support.v4.app.NotificationCompat.Action)">{@code addAction}</a>
+ method to add this action to the notification.
+
+<pre>
+// Android Wear 2.0 requires a hint to display the reply action inline.
+ Action.WearableExtender actionExtender =
+ new Action.WearableExtender()
+ .setHintLaunchesActivity(true)
+ .setHintDisplayActionInline(true);
+ wearableExtender.addAction(actionBuilder.extend(actionExtender).build());
+</pre>
+ </li>
+</ol>
+
<h2 id="expanded">Expanded Notifications</h2>
<p>Android Wear 2.0 introduces <i>expanded notifications</i>, which provide
substantial additional content and actions for each notification.
@@ -152,51 +232,52 @@
</p>
<h2 id="messaging">MessagingStyle</h2>
-<p>If you have a chat messaging app, your notifications should use
-<a href="{@docRoot}preview/features/notification-updates.html#style">{@code Notification.MessagingStyle}</a>,
- which is new in Android N. Wear 2.0 uses the chat messages included
- in a <a href="{@docRoot}preview/features/notification-updates.html#style">{@code MessagingStyle}</a> notification
-
- (see <a href="{@docRoot}preview/features/notification-updates.html#style">{@code addMessage()}</a>) to provide
- a rich chat app-like experience in the expanded notification.
+<p>
+ If you have a chat messaging app, your notifications should use
+ <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.html">{@code NotificationCompat.MessagingStyle}</a>,
+ which is new in Android 7.0. Wear 2.0 uses the chat messages included in a
+ {@code MessagingStyle} notification
+ (see <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.html#addMessage(android.support.v4.app.NotificationCompat.MessagingStyle.Message)">{@code addMessage()}</a>)
+ to provide a rich chat app-like experience in the expanded notification.
</p>
-<p class="note">Note: <a href="{@docRoot}preview/features/notification-updates.html#style">{@code MessagingStyle}</a>
+<p class="note">Note: <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.html">{@code MessagingStyle}</a>
expanded notifications require that you have at least version 1.5.0.2861804 of the
<a href="https://play.google.com/store/apps/details?id=com.google.android.wearable.app">Android Wear app</a>
- on your paired Android phone. That version will be available within the next
- few weeks in the Play Store.
+ on your paired Android phone.
</p>
<h3 id="smart-reply">Smart Reply</h3>
<img src="{@docRoot}wear/preview/images/messaging_style.png" height="420"
style="float:right;margin:10px 20px 0 0" />
-<p>Wear 2.0 also introduces <i>Smart Reply</i>
-for <a href="{@docRoot}preview/features/notification-updates.html#style">{@code MessagingStyle}</a> notifications.
+<p>Wear 2.0 also introduces <i>Smart Reply</i> for
+ <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.html">{@code MessagingStyle}</a> notifications.
Smart Reply provides the user with contextually relevant, touchable choices in
the expanded notification and in {@code RemoteInput}. These augment the fixed
list of choices that the developer provides in
- <a href="http://developer.android.com/reference/android/support/v4/app/RemoteInput.html">{@code RemoteInput}</a>
- using the
- <a href="{@docRoot}reference/android/support/v4/app/RemoteInput.Builder.html#setChoices(java.lang.CharSequence[])">{@code setChoices()}</a> method.
-</p>
-<p>By enabling Smart Reply for your MessagingStyle notifications,
- you provide users with a fast (single tap), discreet (no speaking aloud), and
- reliable way to respond to chat messages.
-</p>
-
-<p>Responses generated by Smart Reply are shown in addition to those set using the
+ <a href="http://developer.android.com/reference/android/support/v4/app/RemoteInput.html">{@code RemoteInput}</a>
+ using the
<a href="{@docRoot}reference/android/support/v4/app/RemoteInput.Builder.html#setChoices(java.lang.CharSequence[])">{@code setChoices()}</a> method.
</p>
+<p> Smart Reply provides users with a fast (single tap), discreet (no speaking aloud),
+ private (messages received by a user never leave the watch), and reliable (no
+ internet connection needed) way to respond to chat messages.
+</p>
+
+<p>
+ Smart Reply responses are generated by an entirely on-watch machine learning
+ model using the context provided by the MessagingStyle notification. No user
+ notification data is sent to Google servers to generate Smart Reply responses.
+</p>
+
<p>To enable Smart Reply for your notification action, you need to do the
following:
</p>
<ol>
- <li>Use <a href="{@docRoot}preview/features/notification-updates.html#style">{@code Notification.MessagingStyle}</a>.
+ <li>Use <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.html">{@code NotificationCompat.MessagingStyle}</a>.
</li>
- <li>Call the method {@code setAllowGeneratedReplies()} for the notification action.
- For more information, see the downloadable
- <a href="{@docRoot}preview/setup-sdk.html#docs-dl">API reference</a>.
+ <li>Call the method <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Action.Builder.html#setAllowGeneratedReplies(boolean)">{@code setAllowGeneratedReplies(true)}</a>
+ for the notification action.
</li>
<li>Ensure that the notification action has a
<a href="{@docRoot}reference/android/app/RemoteInput.html">{@code RemoteInput}</a>
@@ -236,3 +317,29 @@
// 3) add an action with RemoteInput
.extend(new WearableExtender().addAction(action)).build();
</pre>
+
+<h3 id="images">Adding images to a MessagingStyle notification</h3>
+<p>
+ You can add images to a notification message by setting the appropriate MIME
+ type and placing the URI to the image in {@code NotificationCompat.MessagingStyle.Message.}
+ <a href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.Message.html#setData(java.lang.String, android.net.Uri)">{@code setData()}</a> method.
+</p>
+<p>
+ Here is the code snippet to set data of type image in a notification:
+</p>
+<pre>
+NotificationCompat.MessagingStyle.Message message = new Message("sticker", 1, "Jeff")
+ .setData("image/png", stickerUri);
+
+ NotificationCompat notification = new NotificationCompat.Builder()
+ .setStyle(new NotificationComapt.MessagingStyle("Me")
+ .addMessage(message)
+ .build());
+
+</pre>
+<p>
+ In the above code snippet the variable <code>stickerUri </code>is a Uri that
+ points to a publicly-accessible location, as described <a
+ href="https://developer.android.com/reference/android/support/v4/app/NotificationCompat.MessagingStyle.Message.html">here
+ </a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/wear/preview/features/standalone-apps.jd b/docs/html/wear/preview/features/standalone-apps.jd
new file mode 100644
index 0000000..5c1930d
--- /dev/null
+++ b/docs/html/wear/preview/features/standalone-apps.jd
@@ -0,0 +1,499 @@
+page.title=Standalone Apps
+meta.keywords="wear-preview"
+page.tags="wear-preview"
+page.image=images/cards/card-n-sdk_2x.png
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+
+ <ul>
+ <li><a href="#planning_apps">Planning Your Phone and Watch Apps</a></li>
+ <li><a href="#network_access">Network Access and Cloud Messaging</a></li>
+ <li><a href="#background_services">Using Background Services</a></li>
+ <li><a href="#fcm">Cloud Notifications Using FCM</a></li>
+ <li><a href="#fcm-phone">Notifications from a Companion Phone</a></li>
+ </ul>
+
+</div>
+</div>
+
+ <p>
+ In Android Wear 2.0, apps can work independently of a phone. Users can
+ complete more tasks on a watch, without access to an Android or iOS
+ phone.
+ </p>
+
+ <h2 id="planning_apps">
+ Planning Your Phone and Watch Apps
+ </h2>
+
+ <p>
+ A watch APK targeting Wear 2.0 should not be embedded in a phone APK.
+ For more information, see
+ <a href="{@docRoot}wear/preview/features/app-distribution.html">
+ App Distribution</a>.
+ </p>
+
+ <p>
+ Generally, the minimum and target API level for a standalone app, and
+ for Wear 2.0, is level 24. The minimum SDK level can be 23
+ only if you are using the same APK
+ for Wear 1.0 and 2.0 (and thus have an embedded Wear 1.0 APK).
+ </p>
+
+ <p>
+ If you build a standalone Wear 2.0 APK and will continue to have a
+ Wear 1.0 APK, please do both of the following:
+ </p>
+
+ <ul>
+ <li>Provide a standalone version of the Wear APK, and
+ </li>
+
+ <li>Continue embedding a version of the Wear APK in your phone APK
+ </li>
+ </ul>
+
+ <p>
+ <strong>Caution</strong>: For the Wear 2.0 Developer Preview, if you
+ publish an update to your production phone APK that has removed an embedded
+ Wear APK, production users who update the phone APK before installing
+ your standalone Wear APK will lose their existing Wear app and its data.
+ Therefore, it's important that you continue to embed
+ your watch APK into your phone APK.
+ </p>
+
+ <p>
+ <a href=
+ "https://developer.android.com/training/articles/wear-permissions.html">
+ Run-time permissions</a> are required for standalone apps.
+ </p>
+
+ <h3>
+ Shared code and data storage
+ </h3>
+
+ <p>
+ Code can be shared between a Wear app and a phone app. Optionally, code
+ that is specific to a form factor can be in a separate module.
+ </p>
+
+ <p>
+ For example, common code for networking can be in a shared library.
+ </p>
+
+ <p>
+ You can use standard Android storage APIs to store data locally.
+ For example, you can use
+ the <a href=
+ "https://developer.android.com/reference/android/content/SharedPreferences.html">
+ SharedPreferences APIs</a>, SQLite, or internal storage (as you would in
+ the case of a phone).
+ </p>
+
+ <h3>
+ Detecting your phone app or watch app
+ </h3>
+
+ <p>
+ If a user of your watch app needs your phone app, your watch app can
+ detect if the phone app is available. Using the <a href=
+ "https://developers.google.com/android/reference/com/google/android/gms/wearable/CapabilityApi">
+ CapabilityApi</a>, your phone app or watch app can advertise its presence
+ to a paired device. It can do so statically and dynamically. When an app
+ is on a node in a user's Wear network (i.e., on a phone, paired watch, or
+ in the cloud), the <code>CapabilityApi</code> enables another
+ app to detect if it is installed. For more information, see <a href=
+ "https://developer.android.com/training/wearables/data-layer/messages.html#AdvertiseCapabilities">
+ Advertise capabilities</a>.
+ </p>
+
+ <p>
+ If your phone app is unavailable, your can check if the Play Store is
+ available on the phone, as described below, before directing the user to
+ go to the Play Store (to install your phone app).
+ </p>
+
+ <h4>
+ Checking for Play Store availability on a phone
+ </h4>
+
+ <p>
+ iPhones and some Android phones lack the Play Store. A standalone Wear
+ app can check if the paired phone has the Play Store, before directing a
+ user to go there to install your phone app. The
+ <code>PlayStoreAvailability</code> class contains a
+ <code>getPlayStoreAvailabilityOnPhone()</code> method that enables your
+ Wear app to check if a companion phone has the Play Store.
+ </p>
+
+ <p>
+ More information about the <code>PlayStoreAvailability</code> class is
+ available when you <a href=
+ "https://developer.android.com/wear/preview/start.html#get_the_preview_reference_documentation">
+ download the Android Wear 2.0 Preview Reference</a>. Here is a snippet
+ that uses the <code>getPlayStoreAvailabilityOnPhone()</code> method to
+ determine if the paired phone has the Play Store:
+ </p>
+
+<pre>
+int playStoreAvailabilityOnPhone =
+PlayStoreAvailability.getPlayStoreAvailabilityOnPhone(context);
+</pre>
+
+ <p>
+ The value returned by the <code>getPlayStoreAvailabilityOnPhone()</code>
+ method is one of the following:
+ </p>
+
+ <table>
+ <tr>
+ <th>
+ <strong>Return value</strong>
+ </th>
+ <th>
+ <strong>Description</strong>
+ </th>
+ </tr>
+
+ <tr>
+ <td>
+ <code>PLAY_STORE_ON_PHONE_AVAILABLE</code>
+ </td>
+ <td>
+ The Play Store is available on the companion phone.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>PLAY_STORE_ON_PHONE_UNAVAILABLE</code>
+ </td>
+ <td>
+ The Play Store is not available on the companion phone.
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <code>PLAY_STORE_ON_PHONE_ERROR_UNKNOWN</code>
+ </td>
+ <td>
+ An error occurred in the check for the Play Store; another check
+ should be made later.
+ </td>
+ </tr>
+ </table>
+
+ <h2 id="network_access">
+ Network Access and Cloud Messaging
+ </h2>
+
+ <p>
+ Android Wear apps can make their own network requests. When a watch has a
+ Bluetooth connection to a phone, the watch's network traffic is proxied
+ through the phone. When a phone is unavailable, Wi-Fi and cellular
+ networks are used, depending on the hardware. The Wear platform handles
+ transitions between networks. A watch's network access thus does not
+ require the <a href=
+ "https://developer.android.com/training/wearables/data-layer/index.html">
+ Wearable Data Layer API</a>.
+ </p>
+
+ <p>
+ For sending notifications, apps can directly use Firebase Cloud Messaging
+ (FCM), which replaces Google Cloud Messaging, or continue to use GCM.
+ </p>
+
+ <p>
+ No APIs for network access or FCM are specific to Android Wear.
+ Refer to the existing documentation about <a href=
+ "https://developer.android.com/training/basics/network-ops/connecting.html">
+ connecting to a network</a> and <a href=
+ "https://developers.google.com/cloud-messaging/">cloud messaging</a>.
+ </p>
+
+ <p>
+ You can use protocols such as HTTP, TCP, and UDP. However,
+ the <a href="https://developer.android.com/reference/android/webkit/package-summary.html">
+ android.webkit</a> APIs are not available. Therefore,
+ use of cookies is available by reading and writing headers on
+ requests and responses, but the <a href=
+ "https://developer.androidcom/reference/android/webkit/CookieManager.html">
+ CookieManager</a> class is not available.
+ </p>
+
+ <p>
+ FCM works well with
+ <a href="https://developer.android.com/training/monitoring-device-state/doze-standby.html">
+ Doze</a>.
+ </p>
+
+ <p>
+ Additionally, we recommend using the following:
+ </p>
+
+ <ul>
+ <li>The <a href=
+ "https://developer.android.com/reference/android/app/job/JobScheduler.html">
+ JobScheduler</a> API for asynchronous jobs, including polling at
+ regular intervals (described below)
+ </li>
+
+ <li>Multi-networking APIs if you need to connect to specific network
+ types; see <a href=
+ "https://developer.android.com/about/versions/android-5.0.html#Wireless">
+ Multiple Network Connections</a>
+ </li>
+ </ul>
+
+ <p>
+ For foreground use cases, we currently recommend that you make a
+ request for an unmetered network. Here is an example of using
+ the multi-networking APIs to request an unmetered network:
+ </p>
+
+<pre>
+ConnectivityManager.NetworkCallback networkCallback =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ // access network
+ }
+ };
+ConnectivityManager connectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+connectivityManager.requestNetwork(new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .build(), networkCallback);
+</pre>
+
+ <p>
+ We also recommend setting a timer for frontend scenarios
+ to prevent a user from potentially waiting for a long time.
+ When the network is no longer needed, or if the timer fires,
+ the network callback needs to be unregistered:
+ </p>
+
+<pre>
+connectivityManager.unregisterNetworkCallback(networkCallback):
+</pre>
+
+ <p>
+ A Wear app can communicate with a phone app using the <a href=
+ "https://developer.android.com/training/wearables/data-layer/index.html">Wearable
+ Data Layer API</a>, but connecting to a network using that API is
+ discouraged.
+ </p>
+
+ <h3 id="necessary_data">
+ Obtaining only the necessary data
+ </h3>
+
+ <p>
+ When obtaining data from the cloud, get only the necessary data.
+ Otherwise, you may introduce unnecessary latency, memory use, and battery
+ use.
+ </p>
+
+ <p>
+ When a watch is connected over a Bluetooth LE connection, your app may
+ have access to a bandwidth of only 10 kilobytes per second. Therefore,
+ the following steps are recommended:
+ </p>
+
+ <ul>
+ <li>Audit your network requests and responses for extra data that only is
+ for a phone app
+ </li>
+
+ <li>Shrink large images before sending them over a network to a watch
+ </li>
+ </ul>
+
+ <h2 id="background_services">
+ Using Background Services
+ </h2>
+
+ <p>
+ To ensure that background tasks are correctly executed, they must account
+ for <a href=
+ "https://developer.android.com/training/monitoring-device-state/doze-standby.html">
+ Doze</a>. In Android 6.0, Doze and App Standby resulted in significant
+ improvements to battery life by allowing devices to enter deep sleep when
+ idle and stationary.
+ </p>
+
+ <p>
+ Doze is <a href=
+ "https://developer.android.com/preview/behavior-changes.html#doze">enhanced</a>
+ in Android Nougat and Android Wear 2.0. When a screen turns off or enters
+ ambient mode for a long enough time, a subset of Doze can occur and
+ background tasks may be deferred for certain periods. Later, when a
+ device is stationary for an extended time, regular Doze occurs.
+ </p>
+
+ <p>
+ You should schedule jobs with the <a href=
+ "https://developer.android.com/reference/android/app/job/JobScheduler.html">
+ JobScheduler</a> API, which enables your app to register for Doze-safe
+ code execution. When scheduling jobs, you can select constraints such as
+ periodic execution and the need for connectivity or device charging.
+ It is important to configure jobs in a way that does not adversely
+ impact battery life. Jobs should use a
+ <a href="https://developer.android.com/reference/android/app/job/JobInfo.Builder.html">
+ JobInfo.Builder</a> object to provide constraints and metadata, e.g. with
+ one or more of the following methods for a task:
+ </p>
+
+ <ul>
+ <li>To schedule a task that requires networking, use
+ <code>setRequiredNetworkType(int networkType)</code>, specifying
+ <code>NETWORK_TYPE_ANY</code> or <code>NETWORK_TYPE_UNMETERED</code>;
+ note that <code>NETWORK_TYPE_UNMETERED</code> is for large data transfers
+ while <code>NETWORK_TYPE_ANY</code> is for small transfers
+ </li>
+
+ <li>To schedule a task while charging, use
+ <code>setRequiresCharging(boolean requiresCharging)</code>
+ </li>
+
+ <li>For specifying that a device is idle for a task, use
+ <code>setRequiresDeviceIdle(boolean requiresDeviceIdle)</code>; this
+ method can be useful for lower-priority background work or
+ synchronization, especially when used with
+ <code>setRequiresCharging</code>
+ </li>
+ </ul>
+
+ <p>
+ Note that some low-bandwidth networks, such as Bluetooth LE, are
+ considered metered.
+ </p>
+
+ <h3>
+ Scheduling with constraints
+ </h3>
+
+ <p>
+ You can schedule a task that requires constraints. In the example below,
+ a <code>JobScheduler</code> object activates <code>MyJobService</code>
+ when the following constraints are met:
+ </p>
+
+ <ul>
+ <li>Unmetered networking
+ </li>
+
+ <li>Device charging
+ </li>
+ </ul>
+
+ <p>
+ You can use the builder method <code>setExtras</code> to attach a bundle
+ of app-specific metadata to the job request. When your job executes, this
+ bundle is provided to your job service. Note the <code>MY_JOB_ID</code>
+ value passed to the <code>JobInfo.Builder</code> constructor. This
+ <code>MY_JOB_ID</code> value is an app-provided identifier. Subsequent
+ calls to cancel, and subsequent jobs created with that same value, will
+ update the existing job:
+ </p>
+
+<pre>
+JobInfo jobInfo = new JobInfo.Builder(MY_JOB_ID,
+ new ComponentName(this, MyJobService.class))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setRequiresCharging(true)
+ .setExtras(extras)
+ .build();
+((JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE))
+ .schedule(jobInfo);
+</pre>
+
+ <p>
+ Below is an implementation of <a href=
+ "https://developer.android.com/reference/android/app/job/JobService.html">
+ JobService</a> to handle the job above. When the job executes, a
+ <code>JobParameters</code> object is passed into the
+ <code>onStartJob</code> method. The <code>JobParameters</code> object
+ enables you to get the job ID value along with any extras bundle provided
+ when scheduling the job. The <code>onStartJob</code> method is called on
+ the main application thread, and therefore any expensive logic should be
+ run from a separate thread. In the example, an <code>AsyncTask</code> is
+ used to run code in the background. When work is complete, you would call
+ the <code>jobFinished</code> method to notify <code>JobScheduler</code>
+ that the task is done:
+ </p>
+
+<pre>
+public class MyJobService extends JobService {
+ @Override public boolean onStartJob(JobParameters params) {
+ new JobAsyncTask().execute(params);
+ return true;
+ }
+
+ private class JobAsyncTask extends AsyncTask
+</pre>
+
+ <h2 id="fcm">
+ Cloud Notifications Using FCM
+ </h2>
+
+ <p>
+ FCM is the recommended way to send notifications to a watch.
+ </p>
+
+ <p>
+ Provide for messages from FCM by collecting a registration token for a
+ device when your Wear app runs. Then include the token as part of the
+ destination when your server sends messages to the FCM REST endpoint. FCM
+ sends messages to the device identified by the token.
+ </p>
+
+ <p>
+ An FCM message is in JSON format and can include one or both of the
+ following payloads:
+ </p>
+
+ <ul>
+ <li>
+ <strong>Notification payload.</strong> When a notification payload is
+ received by a watch, the data is displayed to a user directly in the
+ notification stream. When the user taps the notification, your app is
+ launched.
+ </li>
+
+ <li>
+ <strong>Data payload</strong>. The payload has a set of custom
+ key/value pairs. The payload and is delivered as data to your Wear app.
+ </li>
+ </ul>
+
+ <p>
+ For more information and examples of payloads, see <a href=
+ "https://firebase.google.com/docs/cloud-messaging/concept-options">About
+ FCM Messages</a>.
+ </p>
+
+ <h2 id="fcm-phone">
+ Notifications from a Companion Phone
+ </h2>
+
+ <p>
+ By default, notifications are bridged (shared) from a phone app to a
+ watch. If you have a standalone Wear app and a corresponding phone app,
+ duplicate notifications can occur. For example, the same notification
+ from FCM, received by both a phone and a watch, could be
+ displayed by both devices independently.
+ </p>
+
+ <p>
+ For information about preventing duplicate notifications, see <a href=
+ "https://developer.android.com/wear/preview/features/bridger.html">Bridging
+ Mode for Notifications</a>.
+ </p>
diff --git a/docs/html/wear/preview/features/wearable-recycler-view.jd b/docs/html/wear/preview/features/wearable-recycler-view.jd
new file mode 100644
index 0000000..f28a472
--- /dev/null
+++ b/docs/html/wear/preview/features/wearable-recycler-view.jd
@@ -0,0 +1,223 @@
+
+page.title=Curved Layout
+meta.tags="wear", "wear-preview", "RecyclerView"
+page.tags="wear"
+
+@jd:body
+
+
+<div id="qv-wrapper">
+<div id="qv">
+
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#creating">Creating a Curved Layout</a></li>
+ <li><a href="#adding">Adding a Circular Scrolling Gesture</a></li>
+ <li><a href="#aligning">Anchoring Children to the Curve</a></li>
+ </ol>
+
+</div>
+</div>
+
+
+<p>
+ Wear 2.0 introduces the {@code WearableRecyclerView} class for displaying
+ and manipulating a vertical list of items optimized for round displays.
+ {@code WearableRecyclerView} extends the existing
+ <a href="{@docRoot}reference/android/support/v7/widget/RecyclerView.html">{@code RecyclerView}</a>
+ class to provide a curved layout and a circular scrolling gesture in wearable apps.
+</p>
+<img src="https://android-dot-devsite.googleplex.com/wear/preview/images/wrv_new.png"
+ style="float:right;margin:10px 20px 0 0">
+
+<p>
+ You can adapt to this interface in your wearable app by creating a new
+ {@code WearableRecyclerView} container.
+</p>
+
+<p>
+ You should decide whether to use a {@code WearableRecyclerView}, based on
+ the kind of user experience you want to provide. We recommend using the
+ {@code WearableRecyclerView} for a simple, long list of items, such as an
+ application launcher, or a list contacts. Each item might have a short string
+ and an associated icon. Alternatively, each item might have only a string or
+ an icon. We do not recommend using a {@code WearableRecyclerView} for short
+ or complex lists.
+</p>
+
+<p>
+ This document describes how to create a curved layout for your scrollable items
+ and properly align them along the curve.
+</p>
+
+
+<h2 id="creating">Creating a Curved Layout</h2>
+<p>To create a curved layout for scrollable items in your wearable app:
+</p>
+<ul>
+ <li>Use {@code WearableRecyclerView} as your main container in the relevant
+ xml layout.
+ </li>
+
+ <li>By default, {@code WearableRecyclerView} uses the {@code
+ DefaultOffsettingHelper} class to offset items in a curved layout on round
+ devices. If you wish to implement your own offsetting logic, you can extend the
+ abstract {@code WearableRecyclerView.OffsettingHelper} class and attach it to
+ the {@code WearableRecyclerView} using {@code
+ WearableRecyclerView.setOffsettingHelper} method.
+
+ <pre>
+ CircularOffsettingHelper circularHelper = new CircularOffsettingHelper();
+ mRecyclerView.setOffsettingHelper(circularHelper);
+ </pre>
+
+ <pre>
+ public class CircularOffsettingHelper extends OffsettingHelper {
+
+ @Override
+ public void updateChild(View child, WearableRecyclerView parent) {
+ int progress = child.getTop() / parent.getHeight();
+ child.setTranslationX(-child.getHeight() * progress);
+ }
+ }
+ </pre>
+
+ </li>
+
+</ul>
+
+<p class="note">
+ <strong>Note:</strong> {@code DefaultOffsettingHelper} class
+ offsets the child items
+ along a predefined UX curve, but the operation can cut off part of the child
+ view if it is not scaled down accordingly. This is because the default curve
+ attempts to fit 5 items on the screen, regardless of their size.
+ If you do not wish to scale your items, you should consider additional padding.
+</p>
+
+<h3>Examples</h3>
+<p>
+ The following code example demonstrates how to add {@code WearableRecyclerView}
+ to a layout:
+</p>
+<pre>
+<android.support.wearable.view.WearableRecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/recycler_launcher_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="vertical" />
+ </pre>
+
+
+<p>
+ To customize the appearance of the children while scrolling (for example,
+ scale the icons and text while the items scroll away from the center), extend
+ the {@code DefaultOffsettingHelper} and override the {@code updateChild }
+ method. It is important to call the {@code super.updateChild(child, parent)} to
+ offset the children along the curve. However, if for any particular child you do
+ not wish them to follow a curve, you can chose not to call the super method for
+ that particular child.
+</p>
+
+<pre>
+
+public class MyOffsettingHelper extends DefaultOffsettingHelper {
+
+ /** How much should we scale the icon at most. */
+ private static final float MAX_ICON_PROGRESS = 0.65f;
+
+ private float mProgressToCenter;
+
+ public OffsettingHelper() {}
+
+ @Override
+
+ public void updateChild(View child, WearableRecyclerView parent) {
+ super.updateChild(child, parent);
+
+
+ // Figure out % progress from top to bottom
+ float centerOffset = ((float) child.getHeight() / 2.0f) / (float) mParentView.getHeight();
+ float yRelativeToCenterOffset = (child.getY() / mParentView.getHeight()) + centerOffset;
+
+ // Normalize for center
+ mProgressToCenter = Math.abs(0.5f - yRelativeToCenterOffset);
+ // Adjust to the maximum scale
+ mProgressToCenter = Math.min(mProgressToCenter, MAX_ICON_PROGRESS);
+
+ child.setScaleX(1 - mProgressToCenter);
+ child.setScaleY(1 - mProgressToCenter);
+ }
+}
+
+
+</pre>
+
+
+<h2 id="adding">Adding a Circular Scrolling Gesture</h2>
+
+<p>
+ By default, circular scrolling is disabled in the {@code
+ WearableRecyclerView}. If you want to enable circular scrolling gesture
+ in your child view, use the {@code WearavleRecyclerView}’s {@code
+ setCircularScrollingGestureEnabled()} method. You can also customize the
+ circular scrolling gesture by defining one or both of the following:
+</p>
+
+<ul>
+ <li>How many degrees the user has to rotate by to scroll through one screen height.
+ This effectively influences the speed of the scolling -
+ {@code setScrollDegreesPerScreen} - the default value is set at 180 degrees.
+ </li>
+
+ <li>
+ The width of a virtual ‘bezel’ near the the edge of the screen in which the
+ gesture will be recognized - {@code setBezelWidth} - the default value is set
+ at 1. This is expressed as a fraction of the radius of the view.
+</ul>
+
+
+<p>The following code snippet shows how to set these methods:</p>
+
+<pre>
+ setCircularScrollingGestureEnabled(true);
+ setBezelWidth(0.5f);
+ setScrollDegreesPerScreen(90);
+</pre>
+
+<h2 id="aligning"> Anchoring Children to the Curve </h2>
+
+<p>
+ To ensure that the layout for WearableRecyclerView is adaptable to different
+ types of child views, the WearableRecyclerView class, by default, chooses the
+ middle left edge (X=0, Y=Half the child height) as the anchor coordinates for
+ the child item. Using the default anchor coordinates can result in offsetting
+ the child items from the left edge of the watch face. To customize the anchor
+ coordinates of your child view along the curve, you can overwrite the
+ {@code adjustAnchorOffsetXY()} method. You can calculate the X (horizontal)
+ and Y (vertical) offset of the child item, and set it using the
+ {@code adjustAnchorOffsetXY()} method to properly align items
+ along the curve. The coordinates should be with relation to the child view.
+</p>
+
+<p><img src="{@docRoot}wear/preview/images/alignment.png"/></p>
+<p><b>Figure 1</b>. Imaginary UX curve and anchor points on the curve.</p>
+
+<p>
+ The code snippet below, calculates the X offset for a child item in which the
+ width of the icon is same as the height of the child item. In this case, the
+ anchor coordinates for the child item are at the center of the icon.
+
+</p>
+<img src="{@docRoot}wear/preview/images/center_align.png" style="float:left;margin:10px 20px 0 0"/>
+
+<pre>
+ @Override
+ protected void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) {
+ anchorOffsetXY[0] = child.getHeight() / 2.0f;
+ }
+</pre>
+
+
diff --git a/docs/html/wear/preview/images/alignment.png b/docs/html/wear/preview/images/alignment.png
new file mode 100644
index 0000000..525b334
--- /dev/null
+++ b/docs/html/wear/preview/images/alignment.png
Binary files differ
diff --git a/docs/html/wear/preview/images/apk-details.png b/docs/html/wear/preview/images/apk-details.png
new file mode 100644
index 0000000..eb3b859
--- /dev/null
+++ b/docs/html/wear/preview/images/apk-details.png
Binary files differ
diff --git a/docs/html/wear/preview/images/apk-tabs.png b/docs/html/wear/preview/images/apk-tabs.png
new file mode 100644
index 0000000..949b98f
--- /dev/null
+++ b/docs/html/wear/preview/images/apk-tabs.png
Binary files differ
diff --git a/docs/html/wear/preview/images/center_align.png b/docs/html/wear/preview/images/center_align.png
new file mode 100644
index 0000000..ca88ad7
--- /dev/null
+++ b/docs/html/wear/preview/images/center_align.png
Binary files differ
diff --git a/docs/html/wear/preview/images/current-apk.png b/docs/html/wear/preview/images/current-apk.png
new file mode 100644
index 0000000..2545f92
--- /dev/null
+++ b/docs/html/wear/preview/images/current-apk.png
Binary files differ
diff --git a/docs/html/wear/preview/images/inline_action.png b/docs/html/wear/preview/images/inline_action.png
new file mode 100644
index 0000000..7ecaafe
--- /dev/null
+++ b/docs/html/wear/preview/images/inline_action.png
Binary files differ
diff --git a/docs/html/wear/preview/images/wrv_new.png b/docs/html/wear/preview/images/wrv_new.png
new file mode 100644
index 0000000..c413c59
--- /dev/null
+++ b/docs/html/wear/preview/images/wrv_new.png
Binary files differ
diff --git a/docs/html/wear/preview/program.jd b/docs/html/wear/preview/program.jd
index e2bf92f..4f2fb5c 100644
--- a/docs/html/wear/preview/program.jd
+++ b/docs/html/wear/preview/program.jd
@@ -143,8 +143,9 @@
<p>
At milestone 4, you'll have access to the final Android Wear 2.0
APIs and SDK to develop with, as well as near-final system images to test
- system behaviors and features. Android Wear 2.0 will use the Android N
- API level at this time. You can begin final compatibility testing of your
+ system behaviors and features. Android Wear 2.0 will use the
+ Android 7.0 API level at this time.
+ You can begin final compatibility testing of your
legacy apps and refine any new code that is using the Android Wear 2.0
APIs or features.
</p>
diff --git a/docs/html/wear/preview/start.jd b/docs/html/wear/preview/start.jd
index 8fccdc8..c9720dc 100644
--- a/docs/html/wear/preview/start.jd
+++ b/docs/html/wear/preview/start.jd
@@ -29,7 +29,7 @@
<p>
If you want an environment for basic compatibility
- testing of your app, you can use your current APK and a
+ testing, you can use your current APK and a
supported watch or an emulator. You don't necessarily need to update your
full development environment to do basic testing. To simply test your
app's compatibility with a preview system image, see <a href=
@@ -48,10 +48,8 @@
</h2>
<p>
- 1. For compatibility with the <a href="{@docRoot}preview/overview.html">N
- Developer Preview</a>, follow the <a href=
- "{@docRoot}preview/setup-sdk.html">setup instructions</a> for installing
- the latest version of Android Studio.
+ 1. For compatibility with Android 7.0, install the latest version of
+ <a href="https://developer.android.com/studio/index.html">Android Studio</a>.
</p>
<p>
@@ -63,7 +61,7 @@
<ul>
<li>Under the <strong>SDK Platforms tab</strong>:
<ul>
- <li>Android N Preview
+ <li>Android 7.0 (Nougat)
</li>
</ul>
</li>
@@ -107,10 +105,10 @@
<tr>
<td>
- <a href="http://storage.googleapis.com/androiddevelopers/shareables/wear-preview/wearable-support-preview-2-docs.zip">wearable-support-preview-2-docs.zip</a>
+ <a href="http://storage.googleapis.com/androiddevelopers/shareables/wear-preview/wearable-support-preview-3-docs.zip">wearable-support-preview-3-docs.zip</a>
</td>
- <td>MD5: afb770c9c5c0431bbcbdde186f1eae06<br>
- SHA-1: 81d681e61cee01f222ea82e83297d23c4e55b8f3
+ <td>MD5: 22bae00e473e39e320aae8ea09a001a5<br>
+ SHA-1: 474502cc7092bcf0bd671441b8654aa8d6c155ed
</td>
</tr>
</table>
@@ -163,7 +161,7 @@
following, which requires that your the Google Repository <a href=
"#install_android_studio_and_the_latest_packages">is the latest
version</a>:
- <code>compile 'com.google.android.support:wearable:2.0.0-alpha2'</code>
+ <code>compile 'com.google.android.support:wearable:2.0.0-alpha3'</code>
</li>
</ul>
</li>
@@ -190,12 +188,12 @@
</li>
<li>Optionally, select the <strong>Phone and Tablet</strong> option. If
- you plan to use N Preview APIs in a phone app, then the Minimum SDK
- option list, select <strong>API N: Android 6.x (N Preview)</strong>.
+ you plan to use Android 7.0 APIs in a phone app, then the Minimum SDK
+ option list, select <strong>API 24: Android 7.0 (Nougat)</strong>.
</li>
<li>Select the <strong>Wear</strong> option, and in the Minimum SDK
- option list, select the latest available (<strong>N Preview</strong>)
+ option list, select the latest available (<strong>API Nougat</strong>)
option. Click <strong>Next</strong> until you exit the Create New Project
wizard.
</li>
@@ -215,7 +213,7 @@
following, which requires that your the Google Repository <a href=
"#install_android_studio_and_the_latest_packages">is the latest
version</a>:
- <code>compile 'com.google.android.support:wearable:2.0.0-alpha2'</code>
+ <code>compile 'com.google.android.support:wearable:2.0.0-alpha3'</code>
</li>
</ul>
</li>
diff --git a/docs/html/wear/preview/support.jd b/docs/html/wear/preview/support.jd
index 78b4e4b..6006627 100644
--- a/docs/html/wear/preview/support.jd
+++ b/docs/html/wear/preview/support.jd
@@ -23,7 +23,9 @@
<ul>
<li><a href="#general">General Advisories</a></li>
+ <li><a href="#platform-version">Platform API Version</a></li>
<li><a href="#deprecations">Deprecations</a></li>
+ <li><a href="#dp3">Developer Preview 3</a></li>
<li><a href="#dp2">Developer Preview 2</a></li>
<li><a href="#dp1">Developer Preview 1</a></li>
</ul>
@@ -46,10 +48,25 @@
panics and crashes.
</li>
<li>Some apps <strong>may not function as expected</strong> on the new
- platform version. This includes Google’s apps and other apps.
+ platform version. This includes Google's apps and other apps.
</li>
</ul>
+<h2 id="platform-version">
+ Platform API Version
+</h2>
+
+<p>
+ The Android Platform API version is incremented to 24 to match Android 7.0.
+ You can update the following in your Android Wear 2.0 Preview project
+ to <strong>24</strong>:
+</p>
+
+<ul>
+ <li><code>compileSdkVersion</code></li>
+ <li><code>targetSdkVersion</code></li>
+</ul>
+
<h2 id="deprecations">Deprecations</h2>
<p>The following fields are deprecated in the preview:</p>
@@ -64,6 +81,291 @@
</li>
</ul>
+<h2 id="dp3">Developer Preview 3</h2>
+
+<div class="wrap">
+ <div class="cols">
+ <div class="col-6of12">
+ <p><em>Date: September 2016<br />
+ Builds: Wearable Support 2.0.0-alpha3, NVE68J<br/>
+ Emulator support: x86 & ARM (32-bit)<br/>
+ </em></p>
+ </div>
+ </div>
+</div>
+
+<h3 id="new-in-fdp3">
+ New in Preview 3
+</h3>
+
+ <p>
+ For access to system images and the companion app for Preview 3, see
+ <a href="https://developer.android.com/wear/preview/downloads.html">
+ Download and Test with a Device</a>.
+ </p>
+
+ <h4>
+ Additions for standalone apps and the Play Store on Wear
+ </h4>
+
+ <p>
+ For information about planning your Wear 2.0 app, see <a href=
+ "https://developer.android.com/wear/preview/features/standalone-apps.html">
+ Standalone Apps</a>.
+ </p>
+
+ <p>
+ Generally, the minimum and target SDK level for Wear 2.0, and for a
+ standalone APK, is level 24. The minimum SDK level can be 23
+ only if you are using the same APK
+ for Wear 1.0 and 2.0 (and thus have an embedded Wear 1.0 APK).
+ </p>
+
+ <p>
+ Run-time permissions are required.
+ </p>
+
+ <p>
+ For information about distributing your Wear 2.0 app, see <a href=
+ "https://developer.android.com/wear/preview/features/app-distribution.html">
+ App Distribution</a>.
+ </p>
+
+ <h4 id="additions-to-the-complications-api">
+ Complications API additions
+ </h4>
+
+ <p>
+ For Preview 3, additions and changes have been made to the Complications
+ API. The <a href=
+ "https://developer.android.com/wear/preview/features/complications.html">documentation</a>
+ includes information about the following additions and changes:
+ </p>
+
+ <ul>
+ <li>To receive complication data and open the provider chooser, a watch
+ face must have the <code>RECEIVE_COMPLICATION_DATA</code> permission.
+ </li>
+
+ <li>To ease a request for the new permission and the starting of the
+ chooser, the <code>ComplicationHelperActivity</code> class is available
+ in the wearable support library. This class should be used instead of
+ <code>ProviderChooserIntent</code> to start the chooser in almost all
+ cases.
+ </li>
+
+ <li>Watch faces can specify default providers that are used until a user
+ selects a provider.
+ </li>
+
+ <li>The complication types used for "empty" data are changed.
+ </li>
+
+ <li>A new permission was added to ensure that only the Android Wear
+ system can bind to provider services.
+ </li>
+ </ul>
+
+ <p>
+ For changes related to the <code>ComplicationData</code> object, see
+ <a href=
+ "https://developer.android.com/wear/preview/behavior-changes.html">Behavior
+ Changes</a>.
+ </p>
+
+ <h4 id="wearable-recycler-view-api">
+ Curved Layout
+ </h4>
+
+ <p>
+ For information about creating a curved layout using
+ the <code>WearableRecyclerView</code> API in your Wear 2.0 app, see
+ <a href="https://developer.android.com/wear/preview/features/wearable-recycler-view.html">
+ Curved Layout</a>.
+ </p>
+
+ <h4 id="notifications-features-fdp3">
+ Notifications features
+ </h4>
+
+ <p>
+ To learn about adding an inline action to a notification,
+ see <a href="https://developer.android.com/wear/preview/notifications.html#inline">Inline
+ Action</a>.
+ </p>
+
+ <p>
+ To learn about adding images to a notification, see
+ <a href=
+ "https://developer.android.com/wear/preview/notifications.html#images">Adding
+ images to a notification</a>.
+ </p>
+
+ <p>
+ For additions related to the bridging of notifications from a companion
+ app to a watch, see <a href=
+ "https://developer.android.com/wear/preview/features/bridger.html">Bridging
+ Mode for Notifications</a>.
+ </p>
+
+ <h4 id="additions-for-smart-reply">
+ Smart Reply additions
+ </h4>
+
+ <p>
+ Smart Reply responses are generated by an entirely on-watch,
+ machine-learning model using the context provided by <a href=
+ "https://developer.android.com/wear/preview/features/notifications.html#messaging">
+ MessagingStyle</a> notifications. Use the <a href=
+ "https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Action.Builder.html#setAllowGeneratedReplies(boolean)">
+ setAllowGeneratedReplies(boolean)</a> method to enable Smart Reply for
+ your <code>MessagingStyle</code> notification.
+ </p>
+
+ <h3 id="known-issues-3">
+ Known Issues
+ </h3>
+
+ <h4 id="notifications">
+ Notifications
+ </h4>
+
+ <ul>
+ <li>The <code>MessagingStyle</code> <a href=
+ "https://developer.android.com/wear/preview/features/notifications.html#images">
+ notifications with images</a> posted by standalone apps don't show
+ images in the notification (i.e., bridged notifications show images,
+ but standalone notifications don't).
+ </li>
+
+ <li>This preview release does not include support for notification
+ groups.
+ </li>
+
+ <li>With Wear 2.0, a watch can receive notifications directly from
+ Firebase Cloud Messaging (FCM), which replaces Google Cloud Messaging
+ (GCM). However, in Preview 3 of Wear 2.0, FCM does not function with
+ iOS-paired watches.
+ </li>
+
+ <li>Smart Reply responses are only shown in <code>RemoteInput</code> when
+ <code>RemoteInput</code> is called from a <code>MessagingStyle</code>
+ expanded notification. Smart Reply responses are not shown in
+ <code>RemoteInput</code> when <code>RemoteInput</code> is called from an
+ <a href=
+ "https://developer.android.com/wear/preview/features/notifications.html#inline">
+ inline action</a> within the stream—an action set with the <a href=
+ "https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Action.WearableExtender.html#setHintDisplayActionInline(boolean)">
+ setHintDisplayActionInline(true)</a> method.
+ </li>
+ </ul>
+
+ <h4 id="companion-app">
+ Companion app
+ </h4>
+
+ <ul>
+ <li>The preview companion app is not compatible with Android 4.3
+ (Jelly Bean MR2), which has an SDK build version code of:
+ <code>JELLY_BEAN_MR2</code></li>
+ </ul>
+
+ <ul>
+ <li>In permission screens in the preview companion app:
+ If you deny a permission, you cannot
+ proceed. Instead of denying a permission, tap <strong>Skip</strong>.
+ </li>
+ </ul>
+
+
+ <h4 id="developer-console">
+ Developer Console
+ </h4>
+
+ <ul>
+ <li>If you set a minimum SDK version of 24, the Play Developer Console
+ states that there are few supported devices.
+ </li>
+ </ul>
+
+ <h4 id="system-user-interface">
+ System user interface and apps
+ </h4>
+
+ <ul>
+ <li>Dismissing multiple notifications can cause an app to forcibly close.
+ </li>
+
+ <li>The "Ok Google" detection and voice transcription may not work
+ reliably.
+ </li>
+
+ <li>Google Fit is not available with Preview 3.
+ </li>
+
+ <li>Syncing for embedded apps is not enabled for the preview. Therefore,
+ to test an app on a device, add it to the Play Store or side-load it
+ onto a watch. Some existing Wear apps, e.g. Google Maps, are only
+ using the embedded apps mechanism currently, and are therefore not
+ installable on the preview (and therefore do not appear on the watch).
+ </li>
+
+ <li>In Play Store search results on the watch,
+ results other than apps sometimes appear.
+ </li>
+
+ <li>Media controls/notifications are not bridged
+ to the watch from an Android KitKat phone.
+ </li>
+ </ul>
+
+ <h4 id="account">
+ Account sync
+ </h4>
+
+ <ul>
+ <li>Account sync initiated from watch settings may not work reliably.
+ Instead, add accounts from the setup flow of the Android Wear app, or using
+ the Accounts settings for a device from the Android Wear app.
+ </li>
+
+ <li>The list of accounts that can be synced is the same as the list of accounts
+ on the phone. So to add a new account, use the Android settings on the phone,
+ and then proceed to Android Wear app to sync that account.
+ </li>
+ </ul>
+
+ <h4 id="devices">
+ Devices
+ </h4>
+
+ <ul>
+ <li>In Android Wear emulators, the Play Store app requires that an
+ account is synced to the device before the app can be opened.
+ </li>
+
+ <li>On the Huawei Watch, selecting the language, followed by multiple
+ acknowledgement dialogues, results in a black screen.
+ </li>
+
+ <li>On the LG Watch Urbane 2nd Edition, when answering a call from the
+ watch, the watch does not provide audio from the caller.
+ </li>
+ </ul>
+
+ <h4 id="smart-reply">
+ Smart Reply
+ </h4>
+
+ <ul>
+ <li>Smart Reply is only available if your watch's system language is
+ English.
+ </li>
+
+ <li>Smart Reply responses are not generated for all messages.
+ </li>
+ </ul>
+
<h2 id="dp2">Developer Preview 2</h2>
<div class="wrap">
@@ -78,24 +380,9 @@
</div>
<h3 id="new-in-fdp2">
- <strong>New in Preview 2</strong>
+ New in Preview 2
</h3>
-<h4 id="platform-version-24">
- Platform API Version
-</h4>
-
-<p>
- The Android Platform API version is incremented to 24 to match Android Nougat.
- You can update the following in your Android Wear 2.0 Preview project
- to <strong>24</strong>:
-</p>
-
-<ul>
- <li><code>compileSdkVersion</code></li>
- <li><code>targetSdkVersion</code></li>
-</ul>
-
<h4 id="wearable-drawers">
Wearable drawers
</h4>
@@ -174,7 +461,7 @@
</p>
<h3 id="known-issues-2">
- <strong>Known Issues</strong>
+ Known Issues
</h3>
<h4 id="notifications-2">
@@ -239,6 +526,10 @@
<li>Unable to turn off the Wi-Fi on a wearable.
</li>
+
+ <li>After music is played on a companion phone,
+ music card notifications are not mirrored to the watch.
+ </li>
</ul>
<h4 id="companion-app-2">
diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java
deleted file mode 100644
index e0a5345..0000000
--- a/graphics/java/android/graphics/Atlas.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/*
- * Copyright (C) 2013 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.graphics;
-
-/**
- * @hide
- */
-public class Atlas {
- /**
- * WARNING: These flag values are part of the on-disk configuration information,
- * do not change their values.
- */
-
- /** DELETED: FLAG_ROTATION = 0x01 */
-
- /**
- * This flag indicates whether the packing algorithm should leave
- * an empty 1 pixel wide border around each bitmap. This border can
- * be useful if the content of the atlas will be used in OpenGL using
- * bilinear filtering.
- */
- public static final int FLAG_ADD_PADDING = 0x2;
- /**
- * Default flags: allow rotations and add padding.
- */
- public static final int FLAG_DEFAULTS = FLAG_ADD_PADDING;
-
- /**
- * Each type defines a different packing algorithm that can
- * be used by an {@link Atlas}. The best algorithm to use
- * will depend on the source dataset and the dimensions of
- * the atlas.
- */
- public enum Type {
- SliceMinArea,
- SliceMaxArea,
- SliceShortAxis,
- SliceLongAxis
- }
-
- /**
- * Represents a bitmap packed in the atlas. Each entry has a location in
- * pixels in the atlas and a rotation flag.
- */
- public static class Entry {
- /**
- * Location, in pixels, of the bitmap on the X axis in the atlas.
- */
- public int x;
- /**
- * Location, in pixels, of the bitmap on the Y axis in the atlas.
- */
- public int y;
- }
-
- private final Policy mPolicy;
-
- /**
- * Creates a new atlas with the specified algorithm and dimensions
- * in pixels. Calling this constructor is equivalent to calling
- * {@link #Atlas(Atlas.Type, int, int, int)} with {@link #FLAG_DEFAULTS}.
- *
- * @param type The algorithm to use to pack rectangles in the atlas
- * @param width The width of the atlas in pixels
- * @param height The height of the atlas in pixels
- *
- * @see #Atlas(Atlas.Type, int, int, int)
- */
- public Atlas(Type type, int width, int height) {
- this(type, width, height, FLAG_DEFAULTS);
- }
-
- /**
- * Creates a new atlas with the specified algorithm and dimensions
- * in pixels. A set of flags can also be specified to control the
- * behavior of the atlas.
- *
- * @param type The algorithm to use to pack rectangles in the atlas
- * @param width The width of the atlas in pixels
- * @param height The height of the atlas in pixels
- * @param flags Optional flags to control the behavior of the atlas:
- * {@link #FLAG_ADD_PADDING}, {@link #FLAG_ALLOW_ROTATIONS}
- *
- * @see #Atlas(Atlas.Type, int, int)
- */
- public Atlas(Type type, int width, int height, int flags) {
- mPolicy = findPolicy(type, width, height, flags);
- }
-
- /**
- * Packs a rectangle of the specified dimensions in this atlas.
- *
- * @param width The width of the rectangle to pack in the atlas
- * @param height The height of the rectangle to pack in the atlas
- *
- * @return An {@link Entry} instance if the rectangle was packed in
- * the atlas, or null if the rectangle could not fit
- *
- * @see #pack(int, int, Atlas.Entry)
- */
- public Entry pack(int width, int height) {
- return pack(width, height, null);
- }
-
- /**
- * Packs a rectangle of the specified dimensions in this atlas.
- *
- * @param width The width of the rectangle to pack in the atlas
- * @param height The height of the rectangle to pack in the atlas
- * @param entry Out parameter that will be filled in with the location
- * and attributes of the packed rectangle, can be null
- *
- * @return An {@link Entry} instance if the rectangle was packed in
- * the atlas, or null if the rectangle could not fit
- *
- * @see #pack(int, int)
- */
- public Entry pack(int width, int height, Entry entry) {
- if (entry == null) entry = new Entry();
- return mPolicy.pack(width, height, entry);
- }
-
- private static Policy findPolicy(Type type, int width, int height, int flags) {
- switch (type) {
- case SliceMinArea:
- return new SlicePolicy(width, height, flags,
- new SlicePolicy.MinAreaSplitDecision());
- case SliceMaxArea:
- return new SlicePolicy(width, height, flags,
- new SlicePolicy.MaxAreaSplitDecision());
- case SliceShortAxis:
- return new SlicePolicy(width, height, flags,
- new SlicePolicy.ShorterFreeAxisSplitDecision());
- case SliceLongAxis:
- return new SlicePolicy(width, height, flags,
- new SlicePolicy.LongerFreeAxisSplitDecision());
- }
- return null;
- }
-
- /**
- * A policy defines how the atlas performs the packing operation.
- */
- private static abstract class Policy {
- abstract Entry pack(int width, int height, Entry entry);
- }
-
- /**
- * The Slice algorightm divides the remaining empty space either
- * horizontally or vertically after a bitmap is placed in the atlas.
- *
- * NOTE: the algorithm is explained below using a tree but is
- * implemented using a linked list instead for performance reasons.
- *
- * The algorithm starts with a single empty cell covering the entire
- * atlas:
- *
- * -----------------------
- * | |
- * | |
- * | |
- * | Empty space |
- * | (C0) |
- * | |
- * | |
- * | |
- * -----------------------
- *
- * The tree of cells looks like this:
- *
- * N0(free)
- *
- * The algorithm then places a bitmap B1, if possible:
- *
- * -----------------------
- * | | |
- * | B1 | |
- * | | |
- * |-------- |
- * | |
- * | |
- * | |
- * | |
- * -----------------------
- *
- * After placing a bitmap in an empty cell, the algorithm splits
- * the remaining space in two new empty cells. The split can occur
- * vertically or horizontally (this is controlled by the "split
- * decision" parameter of the algorithm.)
- *
- * Here is for the instance the result of a vertical split:
- *
- * -----------------------
- * | | |
- * | B1 | |
- * | | |
- * |--------| C2 |
- * | | |
- * | | |
- * | C1 | |
- * | | |
- * -----------------------
- *
- * The cells tree now looks like this:
- *
- * C0(occupied)
- * / \
- * / \
- * / \
- * / \
- * C1(free) C2(free)
- *
- * For each bitmap to place in the atlas, the Slice algorithm
- * will visit the free cells until it finds one where a bitmap can
- * fit. It will then split the now occupied cell and proceed onto
- * the next bitmap.
- */
- private static class SlicePolicy extends Policy {
- private final Cell mRoot = new Cell();
-
- private final SplitDecision mSplitDecision;
-
- private final int mPadding;
-
- /**
- * A cell represents a sub-rectangle of the atlas. A cell is
- * a node in a linked list representing the available free
- * space in the atlas.
- */
- private static class Cell {
- int x;
- int y;
-
- int width;
- int height;
-
- Cell next;
-
- @Override
- public String toString() {
- return String.format("cell[x=%d y=%d width=%d height=%d", x, y, width, height);
- }
- }
-
- SlicePolicy(int width, int height, int flags, SplitDecision splitDecision) {
- mPadding = (flags & FLAG_ADD_PADDING) != 0 ? 1 : 0;
-
- // The entire atlas is empty at first, minus padding
- Cell first = new Cell();
- first.x = first.y = mPadding;
- first.width = width - 2 * mPadding;
- first.height = height - 2 * mPadding;
-
- mRoot.next = first;
- mSplitDecision = splitDecision;
- }
-
- @Override
- Entry pack(int width, int height, Entry entry) {
- Cell cell = mRoot.next;
- Cell prev = mRoot;
-
- while (cell != null) {
- if (insert(cell, prev, width, height, entry)) {
- return entry;
- }
-
- prev = cell;
- cell = cell.next;
- }
-
- return null;
- }
-
- /**
- * Defines how the remaining empty space should be split up:
- * vertically or horizontally.
- */
- private static interface SplitDecision {
- /**
- * Returns true if the remaining space defined by
- * <code>freeWidth</code> and <code>freeHeight</code>
- * should be split horizontally.
- *
- * @param freeWidth The rectWidth of the free space after packing a rectangle
- * @param freeHeight The rectHeight of the free space after packing a rectangle
- * @param rectWidth The rectWidth of the rectangle that was packed in a cell
- * @param rectHeight The rectHeight of the rectangle that was packed in a cell
- */
- boolean splitHorizontal(int freeWidth, int freeHeight,
- int rectWidth, int rectHeight);
- }
-
- // Splits the free area horizontally to minimize the horizontal section area
- private static class MinAreaSplitDecision implements SplitDecision {
- @Override
- public boolean splitHorizontal(int freeWidth, int freeHeight,
- int rectWidth, int rectHeight) {
- return rectWidth * freeHeight > freeWidth * rectHeight;
- }
- }
-
- // Splits the free area horizontally to maximize the horizontal section area
- private static class MaxAreaSplitDecision implements SplitDecision {
- @Override
- public boolean splitHorizontal(int freeWidth, int freeHeight,
- int rectWidth, int rectHeight) {
- return rectWidth * freeHeight <= freeWidth * rectHeight;
- }
- }
-
- // Splits the free area horizontally if the horizontal axis is shorter
- private static class ShorterFreeAxisSplitDecision implements SplitDecision {
- @Override
- public boolean splitHorizontal(int freeWidth, int freeHeight,
- int rectWidth, int rectHeight) {
- return freeWidth <= freeHeight;
- }
- }
-
- // Splits the free area horizontally if the vertical axis is shorter
- private static class LongerFreeAxisSplitDecision implements SplitDecision {
- @Override
- public boolean splitHorizontal(int freeWidth, int freeHeight,
- int rectWidth, int rectHeight) {
- return freeWidth > freeHeight;
- }
- }
-
- /**
- * Attempts to pack a rectangle of specified dimensions in the available
- * empty space.
- *
- * @param cell The cell representing free space in which to pack the rectangle
- * @param prev The previous cell in the free space linked list
- * @param width The width of the rectangle to pack
- * @param height The height of the rectangle to pack
- * @param entry Stores the location of the packged rectangle, if it fits
- *
- * @return True if the rectangle was packed in the atlas, false otherwise
- */
- private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) {
- if (cell.width < width || cell.height < height) {
- return false;
- }
-
- // Remaining free space after packing the rectangle
- int deltaWidth = cell.width - width;
- int deltaHeight = cell.height - height;
-
- // Split the remaining free space into two new cells
- Cell first = new Cell();
- Cell second = new Cell();
-
- first.x = cell.x + width + mPadding;
- first.y = cell.y;
- first.width = deltaWidth - mPadding;
-
- second.x = cell.x;
- second.y = cell.y + height + mPadding;
- second.height = deltaHeight - mPadding;
-
- if (mSplitDecision.splitHorizontal(deltaWidth, deltaHeight,
- width, height)) {
- first.height = height;
- second.width = cell.width;
- } else {
- first.height = cell.height;
- second.width = width;
-
- // The order of the cells matters for efficient packing
- // We want to give priority to the cell chosen by the
- // split decision heuristic
- Cell temp = first;
- first = second;
- second = temp;
- }
-
- // Remove degenerate cases to keep the free list as small as possible
- if (first.width > 0 && first.height > 0) {
- prev.next = first;
- prev = first;
- }
-
- if (second.width > 0 && second.height > 0) {
- prev.next = second;
- second.next = cell.next;
- } else {
- prev.next = cell.next;
- }
-
- // The cell is now completely removed from the free list
- cell.next = null;
-
- // Return the location and rotation of the packed rectangle
- entry.x = cell.x;
- entry.y = cell.y;
-
- return true;
- }
- }
-}
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
new file mode 100644
index 0000000..f135484
--- /dev/null
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -0,0 +1,557 @@
+/*
+ * 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.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.graphics.Canvas.VertexMode;
+import android.text.GraphicsOperations;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.RecordingCanvas;
+
+/**
+ * This class is a base class for Canvas's drawing operations. Any modifications here
+ * should be accompanied by a similar modification to {@link RecordingCanvas}.
+ *
+ * The purpose of this class is to minimize the cost of deciding between regular JNI
+ * and @FastNative JNI to just the virtual call that Canvas already has.
+ *
+ * @hide
+ */
+public abstract class BaseCanvas {
+ /**
+ * Should only be assigned in constructors (or setBitmap if software canvas),
+ * freed by NativeAllocation.
+ */
+ protected long mNativeCanvasWrapper;
+
+ /**
+ * Used to determine when compatibility scaling is in effect.
+ */
+ protected int mScreenDensity = Bitmap.DENSITY_NONE;
+ protected int mDensity = Bitmap.DENSITY_NONE;
+
+ protected void throwIfCannotDraw(Bitmap bitmap) {
+ if (bitmap.isRecycled()) {
+ throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap);
+ }
+ if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 &&
+ bitmap.hasAlpha()) {
+ throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap "
+ + bitmap);
+ }
+ }
+
+ protected final static void checkRange(int length, int offset, int count) {
+ if ((offset | count) < 0 || offset + count > length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ public boolean isHardwareAccelerated() {
+ return false;
+ }
+
+ // ---------------------------------------------------------------------------
+ // Drawing methods
+ // These are also implemented in DisplayListCanvas so that we can
+ // selectively apply on them
+ // Everything below here is copy/pasted from Canvas.java
+ // The JNI registration is handled by android_view_Canvas.cpp
+ // ---------------------------------------------------------------------------
+
+ public void drawArc(float left, float top, float right, float bottom, float startAngle,
+ float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+ nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
+ useCenter, paint.getNativeInstance());
+ }
+
+ public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+ @NonNull Paint paint) {
+ drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
+ paint);
+ }
+
+ public void drawARGB(int a, int r, int g, int b) {
+ drawColor(Color.argb(a, r, g, b));
+ }
+
+ public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
+ throwIfCannotDraw(bitmap);
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top,
+ paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
+ nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Deprecated
+ public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // check for valid input
+ if (width < 0) {
+ throw new IllegalArgumentException("width must be >= 0");
+ }
+ if (height < 0) {
+ throw new IllegalArgumentException("height must be >= 0");
+ }
+ if (Math.abs(stride) < width) {
+ throw new IllegalArgumentException("abs(stride) must be >= width");
+ }
+ int lastScanline = offset + (height - 1) * stride;
+ int length = colors.length;
+ if (offset < 0 || (offset + width > length) || lastScanline < 0
+ || (lastScanline + width > length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // quick escape if there's nothing to draw
+ if (width == 0 || height == 0) {
+ return;
+ }
+ // punch down to native for the actual draw
+ nDrawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Deprecated
+ public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // call through to the common float version
+ drawBitmap(colors, offset, stride, (float) x, (float) y, width, height,
+ hasAlpha, paint);
+ }
+
+ public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+ @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+ @Nullable Paint paint) {
+ if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ if (meshWidth == 0 || meshHeight == 0) {
+ return;
+ }
+ int count = (meshWidth + 1) * (meshHeight + 1);
+ // we mul by 2 since we need two floats per vertex
+ checkRange(verts.length, vertOffset, count * 2);
+ if (colors != null) {
+ // no mul by 2, since we need only 1 color per vertex
+ checkRange(colors.length, colorOffset, count);
+ }
+ nDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
+ verts, vertOffset, colors, colorOffset,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
+ }
+
+ public void drawColor(@ColorInt int color) {
+ nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
+ }
+
+ public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
+ }
+
+ public void drawLine(float startX, float startY, float stopX, float stopY,
+ @NonNull Paint paint) {
+ nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
+ }
+
+ public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
+ drawLines(pts, 0, pts.length, paint);
+ }
+
+ public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
+ nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ public void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+ if (oval == null) {
+ throw new NullPointerException();
+ }
+ drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
+ }
+
+ public void drawPaint(@NonNull Paint paint) {
+ nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
+ }
+
+ public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ public void drawPath(@NonNull Path path, @NonNull Paint paint) {
+ if (path.isSimplePath && path.rects != null) {
+ nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
+ } else {
+ nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
+ }
+ }
+
+ public void drawPoint(float x, float y, @NonNull Paint paint) {
+ nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
+ }
+
+ public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
+ drawPoints(pts, 0, pts.length, paint);
+ }
+
+ @Deprecated
+ public void drawPosText(@NonNull char[] text, int index, int count,
+ @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length || count * 2 > pos.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = 0; i < count; i++) {
+ drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
+ }
+ }
+
+ @Deprecated
+ public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
+ }
+
+ public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+ drawRect(r.left, r.top, r.right, r.bottom, paint);
+ }
+
+ public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper,
+ rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
+ }
+
+ public void drawRGB(int r, int g, int b) {
+ drawColor(Color.rgb(r, g, b));
+ }
+
+ public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+ @NonNull Paint paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
+ paint.getNativeInstance());
+ }
+
+ public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+ drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
+ }
+
+ public void drawText(@NonNull char[] text, int index, int count, float x, float y,
+ @NonNull Paint paint) {
+ if ((index | count | (index + count) |
+ (text.length - index - count)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
+ paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (text instanceof String || text instanceof SpannedString ||
+ text instanceof SpannableString) {
+ nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawText(this, start, end, x, y,
+ paint);
+ } else {
+ char[] buf = TemporaryBuffer.obtain(end - start);
+ TextUtils.getChars(text, start, end, buf, 0);
+ nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+ nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
+ paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ public void drawText(@NonNull String text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
+ paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+ float hOffset, float vOffset, @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
+ path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+ float vOffset, @NonNull Paint paint) {
+ if (text.length() > 0) {
+ nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+ }
+
+ public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+ int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((index | count | contextIndex | contextCount | index - contextIndex
+ | (contextIndex + contextCount) - (index + count)
+ | text.length - (contextIndex + contextCount)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
+ x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ }
+
+ public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+ int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((start | end | contextStart | contextEnd | start - contextStart | end - start
+ | contextEnd - end | text.length() - contextEnd) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (text instanceof String || text instanceof SpannedString ||
+ text instanceof SpannableString) {
+ nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
+ contextEnd, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawTextRun(this, start, end,
+ contextStart, contextEnd, x, y, isRtl, paint);
+ } else {
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
+ int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
+ int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
+ @NonNull Paint paint) {
+ checkRange(verts.length, vertOffset, vertexCount);
+ if (isHardwareAccelerated()) {
+ return;
+ }
+ if (texs != null) {
+ checkRange(texs.length, texOffset, vertexCount);
+ }
+ if (colors != null) {
+ checkRange(colors.length, colorOffset, vertexCount / 2);
+ }
+ if (indices != null) {
+ checkRange(indices.length, indexOffset, indexCount);
+ }
+ nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
+ vertOffset, texs, texOffset, colors, colorOffset,
+ indices, indexOffset, indexCount, paint.getNativeInstance());
+ }
+
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+ long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity);
+
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft,
+ float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
+ float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity);
+
+ private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+ float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero);
+
+ private static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+ private static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+ private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+ private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
+ float stopY, long nativePaint);
+
+ private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ private static native void nDrawOval(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius,
+ long nativePaint);
+
+ private static native void nDrawArc(long nativeCanvas, float left, float top, float right,
+ float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint);
+
+ private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
+ float bottom, float rx, float ry, long nativePaint);
+
+ private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+ private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+ private static native void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero,
+ int screenDensity, int bitmapDensity);
+
+ private static native void nDrawBitmapMatrix(long nativeCanvas, Bitmap bitmap,
+ long nativeMatrix, long nativePaint);
+
+ private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, int meshWidth,
+ int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset,
+ long nativePaint);
+
+ private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
+ int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
+ short[] indices, int indexOffset, int indexCount, long nativePaint);
+
+ private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
+ float x, float y, int flags, long nativePaint, long nativeTypeface);
+
+ private static native void nDrawText(long nativeCanvas, String text, int start, int end,
+ float x, float y, int flags, long nativePaint, long nativeTypeface);
+
+ private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end,
+ int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint,
+ long nativeTypeface);
+
+ private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
+ int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
+ long nativeTypeface);
+
+ private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
+ long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint,
+ long nativeTypeface);
+
+ private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath,
+ float hOffset, float vOffset, int flags, long nativePaint, long nativeTypeface);
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 49721cf..fd2f705 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -48,11 +48,6 @@
// pixel data.
private static final long NATIVE_ALLOCATION_SIZE = 32;
- /**
- * Backing buffer for the Bitmap.
- */
- private byte[] mBuffer;
-
// Convenience for JNI access
private final long mNativePtr;
@@ -78,8 +73,8 @@
private int mHeight;
private boolean mRecycled;
- // Package-scoped for fast access.
- int mDensity = getDefaultDensity();
+ /** @hide */
+ public int mDensity = getDefaultDensity();
private static volatile Matrix sScaleMatrix;
@@ -108,7 +103,7 @@
* int (pointer).
*/
// called from JNI
- Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
+ Bitmap(long nativeBitmap, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
@@ -119,7 +114,6 @@
mHeight = height;
mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;
- mBuffer = buffer;
mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
@@ -128,10 +122,7 @@
}
mNativePtr = nativeBitmap;
- long nativeSize = NATIVE_ALLOCATION_SIZE;
- if (buffer == null) {
- nativeSize += getByteCount();
- }
+ long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);
@@ -139,8 +130,9 @@
/**
* Return the pointer to the native object.
+ * @hide
*/
- long getNativeInstance() {
+ public long getNativeInstance() {
return mNativePtr;
}
@@ -256,12 +248,8 @@
if (!isMutable()) {
throw new IllegalStateException("only mutable bitmaps may be reconfigured");
}
- if (mBuffer == null) {
- throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
- }
- nativeReconfigure(mNativePtr, width, height, config.nativeInt,
- mBuffer.length, mRequestPremultiplied);
+ nativeReconfigure(mNativePtr, width, height, config.nativeInt, mRequestPremultiplied);
mWidth = width;
mHeight = height;
}
@@ -343,7 +331,6 @@
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
- mBuffer = null;
mNinePatchChunk = null;
}
mRecycled = true;
@@ -1273,12 +1260,7 @@
* @see #reconfigure(int, int, Config)
*/
public final int getAllocationByteCount() {
- if (mBuffer == null) {
- // native backed bitmaps don't support reconfiguration,
- // so alloc size is always content size
- return getByteCount();
- }
- return mBuffer.length;
+ return nativeGetAllocationByteCount(mNativePtr);
}
/**
@@ -1695,8 +1677,7 @@
private static native long nativeGetNativeFinalizer();
private static native boolean nativeRecycle(long nativeBitmap);
private static native void nativeReconfigure(long nativeBitmap, int width, int height,
- int config, int allocSize,
- boolean isPremultiplied);
+ int config, boolean isPremultiplied);
private static native boolean nativeCompress(long nativeBitmap, int format,
int quality, OutputStream stream,
@@ -1742,4 +1723,5 @@
private static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1);
private static native long nativeRefPixelRef(long nativeBitmap);
private static native void nativePrepareToDraw(long nativeBitmap);
+ private static native int nativeGetAllocationByteCount(long nativeBitmap);
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index d29005c..b093458 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -26,13 +26,15 @@
import android.text.SpannedString;
import android.text.TextUtils;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.microedition.khronos.opengles.GL;
-import libcore.util.NativeAllocationRegistry;
-
/**
* The Canvas class holds the "draw" calls. To draw something, you need
* 4 basic components: A Bitmap to hold the pixels, a Canvas to host
@@ -46,17 +48,10 @@
* <a href="{@docRoot}guide/topics/graphics/2d-graphics.html">
* Canvas and Drawables</a> developer guide.</p></div>
*/
-public class Canvas {
+public class Canvas extends BaseCanvas {
/** @hide */
public static boolean sCompatibilityRestore = false;
- /**
- * Should only be assigned in constructors (or setBitmap if software canvas),
- * freed by NativeAllocation.
- * @hide
- */
- protected long mNativeCanvasWrapper;
-
/** @hide */
public long getNativeCanvasWrapper() {
return mNativeCanvasWrapper;
@@ -71,18 +66,6 @@
// optional field set by the caller
private DrawFilter mDrawFilter;
- /**
- * @hide
- */
- protected int mDensity = Bitmap.DENSITY_NONE;
-
- /**
- * Used to determine when compatibility scaling is in effect.
- *
- * @hide
- */
- protected int mScreenDensity = Bitmap.DENSITY_NONE;
-
// Maximum bitmap size as defined in Skia's native code
// (see SkCanvas.cpp, SkDraw.cpp)
private static final int MAXMIMUM_BITMAP_SIZE = 32766;
@@ -94,7 +77,7 @@
// Use a Holder to allow static initialization of Canvas in the boot image.
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Canvas.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ Canvas.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
}
// This field is used to finalize the native Canvas properly
@@ -109,7 +92,7 @@
public Canvas() {
if (!isHardwareAccelerated()) {
// 0 means no native bitmap
- mNativeCanvasWrapper = initRaster(null);
+ mNativeCanvasWrapper = nInitRaster(null);
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
} else {
@@ -131,7 +114,7 @@
throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
}
throwIfCannotDraw(bitmap);
- mNativeCanvasWrapper = initRaster(bitmap);
+ mNativeCanvasWrapper = nInitRaster(bitmap);
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
mBitmap = bitmap;
@@ -190,7 +173,7 @@
}
if (bitmap == null) {
- native_setBitmap(mNativeCanvasWrapper, null);
+ nSetBitmap(mNativeCanvasWrapper, null);
mDensity = Bitmap.DENSITY_NONE;
} else {
if (!bitmap.isMutable()) {
@@ -198,7 +181,7 @@
}
throwIfCannotDraw(bitmap);
- native_setBitmap(mNativeCanvasWrapper, bitmap);
+ nSetBitmap(mNativeCanvasWrapper, bitmap);
mDensity = bitmap.mDensity;
}
@@ -207,7 +190,7 @@
/** @hide */
public void setHighContrastText(boolean highContrastText) {
- native_setHighContrastText(mNativeCanvasWrapper, highContrastText);
+ nSetHighContrastText(mNativeCanvasWrapper, highContrastText);
}
/** @hide */
@@ -223,7 +206,7 @@
* @return true if the device that the current layer draws into is opaque
*/
public boolean isOpaque() {
- return native_isOpaque(mNativeCanvasWrapper);
+ return nIsOpaque(mNativeCanvasWrapper);
}
/**
@@ -232,7 +215,7 @@
* @return the width of the current drawing layer
*/
public int getWidth() {
- return native_getWidth(mNativeCanvasWrapper);
+ return nGetWidth(mNativeCanvasWrapper);
}
/**
@@ -241,7 +224,7 @@
* @return the height of the current drawing layer
*/
public int getHeight() {
- return native_getHeight(mNativeCanvasWrapper);
+ return nGetHeight(mNativeCanvasWrapper);
}
/**
@@ -369,7 +352,7 @@
* @return The value to pass to restoreToCount() to balance this save()
*/
public int save() {
- return native_save(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+ return nSave(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
}
/**
@@ -389,7 +372,7 @@
* @return The value to pass to restoreToCount() to balance this save()
*/
public int save(@Saveflags int saveFlags) {
- return native_save(mNativeCanvasWrapper, saveFlags);
+ return nSave(mNativeCanvasWrapper, saveFlags);
}
/**
@@ -441,7 +424,7 @@
*/
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
@Saveflags int saveFlags) {
- return native_saveLayer(mNativeCanvasWrapper, left, top, right, bottom,
+ return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom,
paint != null ? paint.getNativeInstance() : 0,
saveFlags);
}
@@ -501,7 +484,7 @@
public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
@Saveflags int saveFlags) {
alpha = Math.min(255, Math.max(0, alpha));
- return native_saveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom,
+ return nSaveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom,
alpha, saveFlags);
}
@@ -519,7 +502,7 @@
*/
public void restore() {
boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
- native_restore(mNativeCanvasWrapper, throwOnUnderflow);
+ nRestore(mNativeCanvasWrapper, throwOnUnderflow);
}
/**
@@ -527,7 +510,7 @@
* This will equal # save() calls - # restore() calls.
*/
public int getSaveCount() {
- return native_getSaveCount(mNativeCanvasWrapper);
+ return nGetSaveCount(mNativeCanvasWrapper);
}
/**
@@ -545,7 +528,7 @@
*/
public void restoreToCount(int saveCount) {
boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
- native_restoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow);
+ nRestoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow);
}
/**
@@ -556,7 +539,7 @@
*/
public void translate(float dx, float dy) {
if (dx == 0.0f && dy == 0.0f) return;
- native_translate(mNativeCanvasWrapper, dx, dy);
+ nTranslate(mNativeCanvasWrapper, dx, dy);
}
/**
@@ -567,7 +550,7 @@
*/
public void scale(float sx, float sy) {
if (sx == 1.0f && sy == 1.0f) return;
- native_scale(mNativeCanvasWrapper, sx, sy);
+ nScale(mNativeCanvasWrapper, sx, sy);
}
/**
@@ -592,7 +575,7 @@
*/
public void rotate(float degrees) {
if (degrees == 0.0f) return;
- native_rotate(mNativeCanvasWrapper, degrees);
+ nRotate(mNativeCanvasWrapper, degrees);
}
/**
@@ -617,7 +600,7 @@
*/
public void skew(float sx, float sy) {
if (sx == 0.0f && sy == 0.0f) return;
- native_skew(mNativeCanvasWrapper, sx, sy);
+ nSkew(mNativeCanvasWrapper, sx, sy);
}
/**
@@ -627,7 +610,7 @@
* @param matrix The matrix to preconcatenate with the current matrix
*/
public void concat(@Nullable Matrix matrix) {
- if (matrix != null) native_concat(mNativeCanvasWrapper, matrix.native_instance);
+ if (matrix != null) nConcat(mNativeCanvasWrapper, matrix.native_instance);
}
/**
@@ -644,7 +627,7 @@
* @see #concat(Matrix)
*/
public void setMatrix(@Nullable Matrix matrix) {
- native_setMatrix(mNativeCanvasWrapper,
+ nSetMatrix(mNativeCanvasWrapper,
matrix == null ? 0 : matrix.native_instance);
}
@@ -660,7 +643,7 @@
*/
@Deprecated
public void getMatrix(@NonNull Matrix ctm) {
- native_getCTM(mNativeCanvasWrapper, ctm.native_instance);
+ nGetCTM(mNativeCanvasWrapper, ctm.native_instance);
}
/**
@@ -689,7 +672,7 @@
* @return true if the resulting clip is non-empty
*/
public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) {
- return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+ return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
op.nativeInt);
}
@@ -702,7 +685,7 @@
* @return true if the resulting clip is non-empty
*/
public boolean clipRect(@NonNull Rect rect, @NonNull Region.Op op) {
- return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+ return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
op.nativeInt);
}
@@ -714,7 +697,7 @@
* @return true if the resulting clip is non-empty
*/
public boolean clipRect(@NonNull RectF rect) {
- return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+ return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
Region.Op.INTERSECT.nativeInt);
}
@@ -726,7 +709,7 @@
* @return true if the resulting clip is non-empty
*/
public boolean clipRect(@NonNull Rect rect) {
- return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+ return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
Region.Op.INTERSECT.nativeInt);
}
@@ -747,7 +730,7 @@
*/
public boolean clipRect(float left, float top, float right, float bottom,
@NonNull Region.Op op) {
- return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt);
+ return nClipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt);
}
/**
@@ -764,7 +747,7 @@
* @return true if the resulting clip is non-empty
*/
public boolean clipRect(float left, float top, float right, float bottom) {
- return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom,
+ return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
Region.Op.INTERSECT.nativeInt);
}
@@ -782,7 +765,7 @@
* @return true if the resulting clip is non-empty
*/
public boolean clipRect(int left, int top, int right, int bottom) {
- return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom,
+ return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
Region.Op.INTERSECT.nativeInt);
}
@@ -794,7 +777,7 @@
* @return true if the resulting is non-empty
*/
public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) {
- return native_clipPath(mNativeCanvasWrapper, path.readOnlyNI(), op.nativeInt);
+ return nClipPath(mNativeCanvasWrapper, path.readOnlyNI(), op.nativeInt);
}
/**
@@ -823,7 +806,7 @@
*/
@Deprecated
public boolean clipRegion(@NonNull Region region, @NonNull Region.Op op) {
- return native_clipRegion(mNativeCanvasWrapper, region.ni(), op.nativeInt);
+ return nClipRegion(mNativeCanvasWrapper, region.ni(), op.nativeInt);
}
/**
@@ -854,7 +837,7 @@
nativeFilter = filter.mNativeInt;
}
mDrawFilter = filter;
- nativeSetDrawFilter(mNativeCanvasWrapper, nativeFilter);
+ nSetDrawFilter(mNativeCanvasWrapper, nativeFilter);
}
/**
@@ -902,7 +885,7 @@
* does not intersect with the canvas' clip
*/
public boolean quickReject(@NonNull RectF rect, @NonNull EdgeType type) {
- return native_quickReject(mNativeCanvasWrapper,
+ return nQuickReject(mNativeCanvasWrapper,
rect.left, rect.top, rect.right, rect.bottom);
}
@@ -922,7 +905,7 @@
* does not intersect with the canvas' clip
*/
public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) {
- return native_quickReject(mNativeCanvasWrapper, path.readOnlyNI());
+ return nQuickReject(mNativeCanvasWrapper, path.readOnlyNI());
}
/**
@@ -947,7 +930,7 @@
*/
public boolean quickReject(float left, float top, float right, float bottom,
@NonNull EdgeType type) {
- return native_quickReject(mNativeCanvasWrapper, left, top, right, bottom);
+ return nQuickReject(mNativeCanvasWrapper, left, top, right, bottom);
}
/**
@@ -961,7 +944,7 @@
* @return true if the current clip is non-empty.
*/
public boolean getClipBounds(@Nullable Rect bounds) {
- return native_getClipBounds(mNativeCanvasWrapper, bounds);
+ return nGetClipBounds(mNativeCanvasWrapper, bounds);
}
/**
@@ -976,966 +959,6 @@
}
/**
- * 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
- * @param b blue component (0..255) of the color to draw onto the canvas
- */
- public void drawRGB(int r, int g, int b) {
- drawColor(Color.rgb(r, g, b));
- }
-
- /**
- * 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
- * @param g green component (0..255) of the color to draw onto the canvas
- * @param b blue component (0..255) of the color to draw onto the canvas
- */
- public void drawARGB(int a, int r, int g, int b) {
- drawColor(Color.argb(a, r, g, b));
- }
-
- /**
- * 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
- */
- public void drawColor(@ColorInt int color) {
- native_drawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
- }
-
- /**
- * 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
- */
- public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
- native_drawColor(mNativeCanvasWrapper, color, mode.nativeInt);
- }
-
- /**
- * 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
- */
- public void drawPaint(@NonNull Paint paint) {
- native_drawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
- }
-
- /**
- * 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
- */
- public void drawPoints(@Size(multiple=2) float[] pts, int offset, int count,
- @NonNull Paint paint) {
- native_drawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
- }
-
- /**
- * Helper for drawPoints() that assumes you want to draw the entire array
- */
- public void drawPoints(@Size(multiple=2) @NonNull float[] pts, @NonNull Paint paint) {
- drawPoints(pts, 0, pts.length, paint);
- }
-
- /**
- * Helper for drawPoints() for drawing a single point.
- */
- public void drawPoint(float x, float y, @NonNull Paint paint) {
- native_drawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
- }
-
- /**
- * 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
- */
- public void drawLine(float startX, float startY, float stopX, float stopY,
- @NonNull Paint paint) {
- native_drawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
- }
-
- /**
- * 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
- */
- public void drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count,
- @NonNull Paint paint) {
- native_drawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
- }
-
- public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) {
- drawLines(pts, 0, pts.length, 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 paint The paint used to draw the rect
- */
- public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
- native_drawRect(mNativeCanvasWrapper,
- rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
- }
-
- /**
- * 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) {
- drawRect(r.left, r.top, r.right, r.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 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) {
- native_drawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
- }
-
- /**
- * 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
- */
- public void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
- if (oval == null) {
- throw new NullPointerException();
- }
- drawOval(oval.left, oval.top, oval.right, oval.bottom, 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) {
- native_drawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
- }
-
- /**
- * 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 radius The radius of the cirle to be drawn
- * @param paint The paint used to draw the circle
- */
- public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
- native_drawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
- }
-
- /**
- * <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
- */
- public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
- @NonNull Paint paint) {
- drawArc(oval.left, oval.top, oval.right, oval.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 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) {
- native_drawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
- useCenter, paint.getNativeInstance());
- }
-
- /**
- * Draw the specified round-rect using the specified paint. The roundrect
- * will be filled or framed based on the Style 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) {
- drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, 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,
- @NonNull Paint paint) {
- native_drawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, paint.getNativeInstance());
- }
-
- /**
- * 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 paint The paint used to draw the path
- */
- public void drawPath(@NonNull Path path, @NonNull Paint paint) {
- if (path.isSimplePath && path.rects != null) {
- native_drawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
- } else {
- native_drawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
- }
- }
-
- /**
- * @hide
- */
- protected void throwIfCannotDraw(Bitmap bitmap) {
- if (bitmap.isRecycled()) {
- throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap);
- }
- if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 &&
- bitmap.hasAlpha()) {
- throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap "
- + bitmap);
- }
- }
-
- /**
- * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
- *
- * @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) {
- Bitmap bitmap = patch.getBitmap();
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
- native_drawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint,
- mDensity, patch.getDensity());
- }
-
- /**
- * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
- *
- * @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) {
- Bitmap bitmap = patch.getBitmap();
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
- native_drawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint,
- mDensity, patch.getDensity());
- }
-
- /**
- * 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)
- */
- public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
- throwIfCannotDraw(bitmap);
- native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top,
- paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity);
- }
-
- /**
- * 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) {
- if (dst == null) {
- throw new NullPointerException();
- }
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
-
- float left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
- bitmap.mDensity);
- }
-
- /**
- * 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 Rect dst,
- @Nullable Paint paint) {
- if (dst == null) {
- throw new NullPointerException();
- }
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
-
- int left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
- bitmap.mDensity);
- }
-
- /**
- * 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 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
- *
- * @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.
- */
- @Deprecated
- public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
- int width, int height, boolean hasAlpha, @Nullable Paint paint) {
- // check for valid input
- if (width < 0) {
- throw new IllegalArgumentException("width must be >= 0");
- }
- if (height < 0) {
- throw new IllegalArgumentException("height must be >= 0");
- }
- if (Math.abs(stride) < width) {
- throw new IllegalArgumentException("abs(stride) must be >= width");
- }
- int lastScanline = offset + (height - 1) * stride;
- int length = colors.length;
- if (offset < 0 || (offset + width > length) || lastScanline < 0
- || (lastScanline + width > length)) {
- throw new ArrayIndexOutOfBoundsException();
- }
- // quick escape if there's nothing to draw
- if (width == 0 || height == 0) {
- return;
- }
- // punch down to native for the actual draw
- native_drawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
- paint != null ? paint.getNativeInstance() : 0);
- }
-
- /**
- * 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.
- */
- @Deprecated
- public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
- int width, int height, boolean hasAlpha, @Nullable Paint paint) {
- // call through to the common float version
- drawBitmap(colors, offset, stride, (float)x, (float)y, width, height,
- hasAlpha, paint);
- }
-
- /**
- * 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) {
- nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
- paint != null ? paint.getNativeInstance() : 0);
- }
-
- /**
- * @hide
- */
- protected static void checkRange(int length, int offset, int count) {
- if ((offset | count) < 0 || offset + count > length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- }
-
- /**
- * 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 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 colorOffset Number of color elements to skip before drawing
- * @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,
- @Nullable Paint paint) {
- if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
- throw new ArrayIndexOutOfBoundsException();
- }
- if (meshWidth == 0 || meshHeight == 0) {
- return;
- }
- int count = (meshWidth + 1) * (meshHeight + 1);
- // we mul by 2 since we need two floats per vertex
- checkRange(verts.length, vertOffset, count * 2);
- if (colors != null) {
- // no mul by 2, since we need only 1 color per vertex
- checkRange(colors.length, colorOffset, count);
- }
- nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
- verts, vertOffset, colors, colorOffset,
- paint != null ? paint.getNativeInstance() : 0);
- }
-
- public enum VertexMode {
- TRIANGLES(0),
- TRIANGLE_STRIP(1),
- TRIANGLE_FAN(2);
-
- VertexMode(int nativeInt) {
- this.nativeInt = nativeInt;
- }
-
- /**
- * @hide
- */
- public final int nativeInt;
- }
-
- /**
- * 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 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 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 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 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.
- */
- public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
- int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
- int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
- @NonNull Paint paint) {
- checkRange(verts.length, vertOffset, vertexCount);
- if (isHardwareAccelerated()) {
- return;
- }
- if (texs != null) {
- checkRange(texs.length, texOffset, vertexCount);
- }
- if (colors != null) {
- checkRange(colors.length, colorOffset, vertexCount / 2);
- }
- if (indices != null) {
- checkRange(indices.length, indexOffset, indexCount);
- }
- nativeDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
- vertOffset, texs, texOffset, colors, colorOffset,
- indices, indexOffset, indexCount, paint.getNativeInstance());
- }
-
- /**
- * 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 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,
- @NonNull Paint paint) {
- if ((index | count | (index + count) |
- (text.length - index - count)) < 0) {
- throw new IndexOutOfBoundsException();
- }
- native_drawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
- paint.getNativeInstance(), paint.mNativeTypeface);
- }
-
- /**
- * 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 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) {
- native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
- paint.getNativeInstance(), paint.mNativeTypeface);
- }
-
- /**
- * 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 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,
- @NonNull Paint paint) {
- if ((start | end | (end - start) | (text.length() - end)) < 0) {
- throw new IndexOutOfBoundsException();
- }
- native_drawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
- paint.getNativeInstance(), paint.mNativeTypeface);
- }
-
- /**
- * 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 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) {
- if ((start | end | (end - start) | (text.length() - end)) < 0) {
- throw new IndexOutOfBoundsException();
- }
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- native_drawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawText(this, start, end, x, y,
- paint);
- } else {
- char[] buf = TemporaryBuffer.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- native_drawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
- TemporaryBuffer.recycle(buf);
- }
- }
-
- /**
- * 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 -
- * 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 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
- */
- public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
- int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
-
- if (text == null) {
- throw new NullPointerException("text is null");
- }
- if (paint == null) {
- throw new NullPointerException("paint is null");
- }
- if ((index | count | contextIndex | contextCount | index - contextIndex
- | (contextIndex + contextCount) - (index + count)
- | text.length - (contextIndex + contextCount)) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- native_drawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
- x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
- }
-
- /**
- * 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
- * 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
- * {@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
- * 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.
- *
- * @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 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,
- int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
-
- if (text == null) {
- throw new NullPointerException("text is null");
- }
- if (paint == null) {
- throw new NullPointerException("paint is null");
- }
- if ((start | end | contextStart | contextEnd | start - contextStart | end - start
- | contextEnd - end | text.length() - contextEnd) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- native_drawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
- contextEnd, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawTextRun(this, start, end,
- contextStart, contextEnd, x, y, isRtl, paint);
- } else {
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- native_drawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
- 0, contextLen, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
- TemporaryBuffer.recycle(buf);
- }
- }
-
- /**
- * 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).
- */
- @Deprecated
- public void drawPosText(@NonNull char[] text, int index, int count,
- @NonNull @Size(multiple=2) float[] pos,
- @NonNull Paint paint) {
- if (index < 0 || index + count > text.length || count*2 > pos.length) {
- throw new IndexOutOfBoundsException();
- }
- for (int i = 0; i < count; i++) {
- drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
- }
- }
-
- /**
- * 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 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 String text, @NonNull @Size(multiple=2) float[] pos,
- @NonNull Paint paint) {
- drawPosText(text.toCharArray(), 0, text.length(), pos, 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) {
- if (index < 0 || index + count > text.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- native_drawTextOnPath(mNativeCanvasWrapper, text, index, count,
- path.readOnlyNI(), hOffset, vOffset,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
- }
-
- /**
- * 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 String text, @NonNull Path path, float hOffset,
- float vOffset, @NonNull Paint paint) {
- if (text.length() > 0) {
- native_drawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
- }
- }
-
- /**
* Save the canvas state, draw the picture, and restore the canvas state.
* This differs from picture.draw(canvas), which does not perform any
* save/restore.
@@ -1980,6 +1003,21 @@
restore();
}
+ public enum VertexMode {
+ TRIANGLES(0),
+ TRIANGLE_STRIP(1),
+ TRIANGLE_FAN(2);
+
+ VertexMode(int nativeInt) {
+ this.nativeInt = nativeInt;
+ }
+
+ /**
+ * @hide
+ */
+ public final int nativeInt;
+ }
+
/**
* Releases the resources associated with this canvas.
*
@@ -1998,165 +1036,792 @@
*
* @hide
*/
- public static native void freeCaches();
+ public static void freeCaches() {
+ nFreeCaches();
+ }
/**
* Free up text layout caches
*
* @hide
*/
- public static native void freeTextLayoutCaches();
+ public static void freeTextLayoutCaches() {
+ nFreeTextLayoutCaches();
+ }
- private static native long initRaster(Bitmap bitmap);
- private static native void native_setBitmap(long canvasHandle,
+ private static native void nFreeCaches();
+ private static native void nFreeTextLayoutCaches();
+ private static native long nInitRaster(Bitmap bitmap);
+ private static native long nGetNativeFinalizer();
+
+ // ---------------- @FastNative -------------------
+
+ @FastNative
+ private static native void nSetBitmap(long canvasHandle,
Bitmap bitmap);
- private static native boolean native_isOpaque(long canvasHandle);
- private static native void native_setHighContrastText(long renderer, boolean highContrastText);
- private static native int native_getWidth(long canvasHandle);
- private static native int native_getHeight(long canvasHandle);
+ @FastNative
+ private static native boolean nIsOpaque(long canvasHandle);
+ @FastNative
+ private static native void nSetHighContrastText(long renderer, boolean highContrastText);
+ @FastNative
+ private static native int nGetWidth(long canvasHandle);
+ @FastNative
+ private static native int nGetHeight(long canvasHandle);
- private static native int native_save(long canvasHandle, int saveFlags);
- private static native int native_saveLayer(long nativeCanvas, float l,
+ @FastNative
+ private static native int nSave(long canvasHandle, int saveFlags);
+ @FastNative
+ private static native int nSaveLayer(long nativeCanvas, float l,
float t, float r, float b,
long nativePaint,
int layerFlags);
- private static native int native_saveLayerAlpha(long nativeCanvas, float l,
+ @FastNative
+ private static native int nSaveLayerAlpha(long nativeCanvas, float l,
float t, float r, float b,
int alpha, int layerFlags);
- private static native void native_restore(long canvasHandle, boolean tolerateUnderflow);
- private static native void native_restoreToCount(long canvasHandle,
+ @FastNative
+ private static native void nRestore(long canvasHandle, boolean tolerateUnderflow);
+ @FastNative
+ private static native void nRestoreToCount(long canvasHandle,
int saveCount,
boolean tolerateUnderflow);
- private static native int native_getSaveCount(long canvasHandle);
+ @FastNative
+ private static native int nGetSaveCount(long canvasHandle);
- private static native void native_translate(long canvasHandle,
+ @FastNative
+ private static native void nTranslate(long canvasHandle,
float dx, float dy);
- private static native void native_scale(long canvasHandle,
+ @FastNative
+ private static native void nScale(long canvasHandle,
float sx, float sy);
- private static native void native_rotate(long canvasHandle, float degrees);
- private static native void native_skew(long canvasHandle,
+ @FastNative
+ private static native void nRotate(long canvasHandle, float degrees);
+ @FastNative
+ private static native void nSkew(long canvasHandle,
float sx, float sy);
- private static native void native_concat(long nativeCanvas,
+ @FastNative
+ private static native void nConcat(long nativeCanvas,
long nativeMatrix);
- private static native void native_setMatrix(long nativeCanvas,
+ @FastNative
+ private static native void nSetMatrix(long nativeCanvas,
long nativeMatrix);
- private static native boolean native_clipRect(long nativeCanvas,
+ @FastNative
+ private static native boolean nClipRect(long nativeCanvas,
float left, float top,
float right, float bottom,
int regionOp);
- private static native boolean native_clipPath(long nativeCanvas,
+ @FastNative
+ private static native boolean nClipPath(long nativeCanvas,
long nativePath,
int regionOp);
- private static native boolean native_clipRegion(long nativeCanvas,
+ @FastNative
+ private static native boolean nClipRegion(long nativeCanvas,
long nativeRegion,
int regionOp);
- private static native void nativeSetDrawFilter(long nativeCanvas,
+ @FastNative
+ private static native void nSetDrawFilter(long nativeCanvas,
long nativeFilter);
- private static native boolean native_getClipBounds(long nativeCanvas,
+ @FastNative
+ private static native boolean nGetClipBounds(long nativeCanvas,
Rect bounds);
- private static native void native_getCTM(long nativeCanvas,
+ @FastNative
+ private static native void nGetCTM(long nativeCanvas,
long nativeMatrix);
- private static native boolean native_quickReject(long nativeCanvas,
+ @FastNative
+ private static native boolean nQuickReject(long nativeCanvas,
long nativePath);
- private static native boolean native_quickReject(long nativeCanvas,
+ @FastNative
+ private static native boolean nQuickReject(long nativeCanvas,
float left, float top,
float right, float bottom);
- private static native void native_drawColor(long nativeCanvas, int color,
- int mode);
- private static native void native_drawPaint(long nativeCanvas,
- long nativePaint);
- private static native void native_drawPoint(long canvasHandle, float x, float y,
- long paintHandle);
- private static native void native_drawPoints(long canvasHandle, float[] pts,
- int offset, int count,
- long paintHandle);
- private static native void native_drawLine(long nativeCanvas, float startX,
- float startY, float stopX,
- float stopY, long nativePaint);
- private static native void native_drawLines(long canvasHandle, float[] pts,
- int offset, int count,
- long paintHandle);
- private static native void native_drawRect(long nativeCanvas, float left,
- float top, float right,
- float bottom,
- long nativePaint);
- private static native void native_drawOval(long nativeCanvas, float left, float top,
- float right, float bottom, long nativePaint);
- private static native void native_drawCircle(long nativeCanvas, float cx,
- float cy, float radius,
- long nativePaint);
- private static native void native_drawArc(long nativeCanvas, float left, float top,
- float right, float bottom,
- float startAngle, float sweep, boolean useCenter,
- long nativePaint);
- private static native void native_drawRoundRect(long nativeCanvas,
- float left, float top, float right, float bottom,
- float rx, float ry, long nativePaint);
- private static native void native_drawPath(long nativeCanvas,
- long nativePath,
- long nativePaint);
- private static native void native_drawRegion(long nativeCanvas,
- long nativeRegion, long nativePaint);
- private native void native_drawNinePatch(long nativeCanvas, long nativeBitmap,
- long ninePatch, float dstLeft, float dstTop, float dstRight, float dstBottom,
- long nativePaintOrZero, int screenDensity, int bitmapDensity);
- private native void native_drawBitmap(long nativeCanvas, Bitmap bitmap,
- float left, float top,
- long nativePaintOrZero,
- int canvasDensity,
- int screenDensity,
- int bitmapDensity);
- private native void native_drawBitmap(long nativeCanvas, Bitmap bitmap,
- float srcLeft, float srcTop, float srcRight, float srcBottom,
- float dstLeft, float dstTop, float dstRight, float dstBottom,
- long nativePaintOrZero, int screenDensity, int bitmapDensity);
- private static native void native_drawBitmap(long nativeCanvas, int[] colors,
- int offset, int stride, float x,
- float y, int width, int height,
- boolean hasAlpha,
- long nativePaintOrZero);
- private static native void nativeDrawBitmapMatrix(long nativeCanvas,
- Bitmap bitmap,
- long nativeMatrix,
- long nativePaint);
- private static native void nativeDrawBitmapMesh(long nativeCanvas,
- Bitmap bitmap,
- int meshWidth, int meshHeight,
- float[] verts, int vertOffset,
- int[] colors, int colorOffset,
- long nativePaint);
- private static native void nativeDrawVertices(long nativeCanvas, int mode, int n,
- float[] verts, int vertOffset, float[] texs, int texOffset,
- int[] colors, int colorOffset, short[] indices,
- int indexOffset, int indexCount, long nativePaint);
- private static native void native_drawText(long nativeCanvas, char[] text,
- int index, int count, float x,
- float y, int flags, long nativePaint,
- long nativeTypeface);
- private static native void native_drawText(long nativeCanvas, String text,
- int start, int end, float x,
- float y, int flags, long nativePaint,
- long nativeTypeface);
+ /**
+ * <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
+ */
+ public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+ @NonNull Paint paint) {
+ super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
+ }
- private static native void native_drawTextRun(long nativeCanvas, String text,
- int start, int end, int contextStart, int contextEnd,
- float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
+ /**
+ * <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);
+ }
- private static native void native_drawTextRun(long nativeCanvas, char[] text,
- int start, int count, int contextStart, int contextCount,
- float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
+ /**
+ * 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
+ * @param g green component (0..255) of the color to draw onto the canvas
+ * @param b blue component (0..255) of the color to draw onto the canvas
+ */
+ public void drawARGB(int a, int r, int g, int b) {
+ super.drawARGB(a, r, g, b);
+ }
- private static native void native_drawTextOnPath(long nativeCanvas,
- char[] text, int index,
- int count, long nativePath,
- float hOffset,
- float vOffset, int bidiFlags,
- long nativePaint, long nativeTypeface);
- private static native void native_drawTextOnPath(long nativeCanvas,
- String text, long nativePath,
- float hOffset,
- float vOffset,
- int flags, long nativePaint, long nativeTypeface);
- private static native long getNativeFinalizer();
+ /**
+ * 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)
+ */
+ public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
+ super.drawBitmap(bitmap, left, top, 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.
+ *
+ * @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);
+ }
+
+ /**
+ * 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 Rect 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.
+ *
+ * @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 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
+ * @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.
+ */
+ @Deprecated
+ public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint);
+ }
+
+ /**
+ * 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.
+ */
+ @Deprecated
+ public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint);
+ }
+
+ /**
+ * 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 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 colorOffset Number of color elements to skip before drawing
+ * @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,
+ @Nullable Paint paint) {
+ super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset,
+ 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 radius The radius of the cirle to be drawn
+ * @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.
+ *
+ * @param color the color to draw onto the canvas
+ */
+ public void drawColor(@ColorInt int color) {
+ super.drawColor(color);
+ }
+
+ /**
+ * 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
+ */
+ 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>
+ *
+ * @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
+ */
+ public void drawLine(float startX, float startY, float stopX, float stopY,
+ @NonNull Paint paint) {
+ super.drawLine(startX, startY, stopX, stopY, paint);
+ }
+
+ /**
+ * 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
+ */
+ 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) {
+ 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.
+ *
+ * @param oval The rectangle bounds of the oval to be drawn
+ */
+ public void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+ super.drawOval(oval, 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
+ */
+ public void drawPaint(@NonNull Paint paint) {
+ super.drawPaint(paint);
+ }
+
+ /**
+ * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
+ *
+ * @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) {
+ super.drawPatch(patch, dst, paint);
+ }
+
+ /**
+ * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
+ *
+ * @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) {
+ super.drawPatch(patch, dst, 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 paint The paint used to draw the path
+ */
+ public void drawPath(@NonNull Path path, @NonNull Paint paint) {
+ super.drawPath(path, paint);
+ }
+
+ /**
+ * Helper for drawPoints() for drawing a single point.
+ */
+ public void drawPoint(float x, float y, @NonNull Paint paint) {
+ super.drawPoint(x, y, paint);
+ }
+
+ /**
+ * 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
+ */
+ public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ super.drawPoints(pts, offset, count, paint);
+ }
+
+ /**
+ * Helper for drawPoints() that assumes you want to draw the entire array
+ */
+ 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.
+ *
+ * @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 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.
+ *
+ * @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
+ 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.
+ *
+ * @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) {
+ super.drawRect(rect, 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 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
+ * @param b blue component (0..255) of the color to draw onto the canvas
+ */
+ public void drawRGB(int r, int g, int b) {
+ super.drawRGB(r, g, b);
+ }
+
+ /**
+ * Draw the specified round-rect using the specified paint. The roundrect will be filled or
+ * framed based on the Style 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 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,
+ @NonNull Paint paint) {
+ super.drawRoundRect(left, top, right, bottom, 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 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,
+ @NonNull Paint paint) {
+ super.drawText(text, index, count, 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 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) {
+ super.drawText(text, 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 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 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,
+ @NonNull Paint paint) {
+ super.drawText(text, start, end, x, y, paint);
+ }
+
+ /**
+ * 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 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) {
+ super.drawTextOnPath(text, index, count, path, hOffset, vOffset, 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 String text, @NonNull Path path, float hOffset,
+ float vOffset, @NonNull Paint paint) {
+ super.drawTextOnPath(text, path, hOffset, vOffset, paint);
+ }
+
+ /**
+ * 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 -
+ * 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 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
+ */
+ public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+ int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+ super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, isRtl, paint);
+ }
+
+ /**
+ * 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
+ * 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
+ * {@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
+ * 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.
+ *
+ * @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 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,
+ int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+ super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint);
+ }
+
+ /**
+ * 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 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 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 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 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.
+ */
+ public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
+ int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
+ int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
+ @NonNull Paint paint) {
+ super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset,
+ colors, colorOffset, indices, indexOffset, indexCount, paint);
+ }
}
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 1e8f11b..486070c 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -16,8 +16,12 @@
package android.graphics;
-import java.io.PrintWriter;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.PrintWriter;
/**
* The Matrix class holds a 3x3 matrix for transforming coordinates.
@@ -216,352 +220,345 @@
}
};
+ // sizeof(SkMatrix) is 9 * sizeof(float) + uint32_t
+ private static final long NATIVE_ALLOCATION_SIZE = 40;
+
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Matrix.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ }
+
/**
* @hide
*/
- public long native_instance;
+ public final long native_instance;
/**
* Create an identity matrix
*/
public Matrix() {
- native_instance = native_create(0);
+ native_instance = nCreate(0);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
/**
* Create a matrix that is a (deep) copy of src
+ *
* @param src The matrix to copy into this matrix
*/
public Matrix(Matrix src) {
- native_instance = native_create(src != null ? src.native_instance : 0);
+ native_instance = nCreate(src != null ? src.native_instance : 0);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
/**
- * Returns true if the matrix is identity.
- * This maybe faster than testing if (getType() == 0)
+ * Returns true if the matrix is identity. This maybe faster than testing if (getType() == 0)
*/
public boolean isIdentity() {
- return native_isIdentity(native_instance);
+ return nIsIdentity(native_instance);
}
/**
- * Gets whether this matrix is affine. An affine matrix preserves
- * straight lines and has no perspective.
+ * Gets whether this matrix is affine. An affine matrix preserves straight lines and has no
+ * perspective.
*
* @return Whether the matrix is affine.
*/
public boolean isAffine() {
- return native_isAffine(native_instance);
+ return nIsAffine(native_instance);
}
/**
- * Returns true if will map a rectangle to another rectangle. This can be
- * true if the matrix is identity, scale-only, or rotates a multiple of 90
- * degrees.
+ * Returns true if will map a rectangle to another rectangle. This can be true if the matrix is
+ * identity, scale-only, or rotates a multiple of 90 degrees.
*/
public boolean rectStaysRect() {
- return native_rectStaysRect(native_instance);
+ return nRectStaysRect(native_instance);
}
/**
- * (deep) copy the src matrix into this matrix. If src is null, reset this
- * matrix to the identity matrix.
+ * (deep) copy the src matrix into this matrix. If src is null, reset this matrix to the
+ * identity matrix.
*/
public void set(Matrix src) {
if (src == null) {
reset();
} else {
- native_set(native_instance, src.native_instance);
+ nSet(native_instance, src.native_instance);
}
}
- /** Returns true iff obj is a Matrix and its values equal our values.
- */
+ /**
+ * Returns true iff obj is a Matrix and its values equal our values.
+ */
@Override
public boolean equals(Object obj) {
- //if (obj == this) return true; -- NaN value would mean matrix != itself
- if (!(obj instanceof Matrix)) return false;
- return native_equals(native_instance, ((Matrix)obj).native_instance);
+ // if (obj == this) return true; -- NaN value would mean matrix != itself
+ if (!(obj instanceof Matrix)) {
+ return false;
+ }
+ return nEquals(native_instance, ((Matrix) obj).native_instance);
}
@Override
public int hashCode() {
// This should generate the hash code by performing some arithmetic operation on all
// the matrix elements -- our equals() does an element-by-element comparison, and we
- // need to ensure that the hash code for two equal objects is the same. We're not
+ // need to ensure that the hash code for two equal objects is the same. We're not
// really using this at the moment, so we take the easy way out.
return 44;
}
/** Set the matrix to identity */
public void reset() {
- native_reset(native_instance);
+ nReset(native_instance);
}
/** Set the matrix to translate by (dx, dy). */
public void setTranslate(float dx, float dy) {
- native_setTranslate(native_instance, dx, dy);
+ nSetTranslate(native_instance, dx, dy);
}
/**
- * Set the matrix to scale by sx and sy, with a pivot point at (px, py).
- * The pivot point is the coordinate that should remain unchanged by the
- * specified transformation.
+ * Set the matrix to scale by sx and sy, with a pivot point at (px, py). The pivot point is the
+ * coordinate that should remain unchanged by the specified transformation.
*/
public void setScale(float sx, float sy, float px, float py) {
- native_setScale(native_instance, sx, sy, px, py);
+ nSetScale(native_instance, sx, sy, px, py);
}
/** Set the matrix to scale by sx and sy. */
public void setScale(float sx, float sy) {
- native_setScale(native_instance, sx, sy);
+ nSetScale(native_instance, sx, sy);
}
/**
- * Set the matrix to rotate by the specified number of degrees, with a pivot
- * point at (px, py). The pivot point is the coordinate that should remain
- * unchanged by the specified transformation.
+ * Set the matrix to rotate by the specified number of degrees, with a pivot point at (px, py).
+ * The pivot point is the coordinate that should remain unchanged by the specified
+ * transformation.
*/
public void setRotate(float degrees, float px, float py) {
- native_setRotate(native_instance, degrees, px, py);
+ nSetRotate(native_instance, degrees, px, py);
}
/**
* Set the matrix to rotate about (0,0) by the specified number of degrees.
*/
public void setRotate(float degrees) {
- native_setRotate(native_instance, degrees);
+ nSetRotate(native_instance, degrees);
}
/**
- * Set the matrix to rotate by the specified sine and cosine values, with a
- * pivot point at (px, py). The pivot point is the coordinate that should
- * remain unchanged by the specified transformation.
+ * Set the matrix to rotate by the specified sine and cosine values, with a pivot point at (px,
+ * py). The pivot point is the coordinate that should remain unchanged by the specified
+ * transformation.
*/
public void setSinCos(float sinValue, float cosValue, float px, float py) {
- native_setSinCos(native_instance, sinValue, cosValue, px, py);
+ nSetSinCos(native_instance, sinValue, cosValue, px, py);
}
/** Set the matrix to rotate by the specified sine and cosine values. */
public void setSinCos(float sinValue, float cosValue) {
- native_setSinCos(native_instance, sinValue, cosValue);
+ nSetSinCos(native_instance, sinValue, cosValue);
}
/**
- * Set the matrix to skew by sx and sy, with a pivot point at (px, py).
- * The pivot point is the coordinate that should remain unchanged by the
- * specified transformation.
+ * Set the matrix to skew by sx and sy, with a pivot point at (px, py). The pivot point is the
+ * coordinate that should remain unchanged by the specified transformation.
*/
public void setSkew(float kx, float ky, float px, float py) {
- native_setSkew(native_instance, kx, ky, px, py);
+ nSetSkew(native_instance, kx, ky, px, py);
}
/** Set the matrix to skew by sx and sy. */
public void setSkew(float kx, float ky) {
- native_setSkew(native_instance, kx, ky);
+ nSetSkew(native_instance, kx, ky);
}
/**
- * Set the matrix to the concatenation of the two specified matrices and
- * return true.
- *
- * <p>Either of the two matrices may also be the target matrix, that is
- * <code>matrixA.setConcat(matrixA, matrixB);</code> is valid.</p>
- *
- * <p class="note">In {@link android.os.Build.VERSION_CODES#GINGERBREAD_MR1} and below, this
- * function returns true only if the result can be represented. In
- * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and above, it always returns true.</p>
+ * Set the matrix to the concatenation of the two specified matrices and return true.
+ * <p>
+ * Either of the two matrices may also be the target matrix, that is
+ * <code>matrixA.setConcat(matrixA, matrixB);</code> is valid.
+ * </p>
+ * <p class="note">
+ * In {@link android.os.Build.VERSION_CODES#GINGERBREAD_MR1} and below, this function returns
+ * true only if the result can be represented. In
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and above, it always returns true.
+ * </p>
*/
public boolean setConcat(Matrix a, Matrix b) {
- native_setConcat(native_instance, a.native_instance, b.native_instance);
+ nSetConcat(native_instance, a.native_instance, b.native_instance);
return true;
}
/**
- * Preconcats the matrix with the specified translation.
- * M' = M * T(dx, dy)
+ * Preconcats the matrix with the specified translation. M' = M * T(dx, dy)
*/
public boolean preTranslate(float dx, float dy) {
- native_preTranslate(native_instance, dx, dy);
+ nPreTranslate(native_instance, dx, dy);
return true;
}
/**
- * Preconcats the matrix with the specified scale.
- * M' = M * S(sx, sy, px, py)
+ * Preconcats the matrix with the specified scale. M' = M * S(sx, sy, px, py)
*/
public boolean preScale(float sx, float sy, float px, float py) {
- native_preScale(native_instance, sx, sy, px, py);
+ nPreScale(native_instance, sx, sy, px, py);
return true;
}
/**
- * Preconcats the matrix with the specified scale.
- * M' = M * S(sx, sy)
+ * Preconcats the matrix with the specified scale. M' = M * S(sx, sy)
*/
public boolean preScale(float sx, float sy) {
- native_preScale(native_instance, sx, sy);
+ nPreScale(native_instance, sx, sy);
return true;
}
/**
- * Preconcats the matrix with the specified rotation.
- * M' = M * R(degrees, px, py)
+ * Preconcats the matrix with the specified rotation. M' = M * R(degrees, px, py)
*/
public boolean preRotate(float degrees, float px, float py) {
- native_preRotate(native_instance, degrees, px, py);
+ nPreRotate(native_instance, degrees, px, py);
return true;
}
/**
- * Preconcats the matrix with the specified rotation.
- * M' = M * R(degrees)
+ * Preconcats the matrix with the specified rotation. M' = M * R(degrees)
*/
public boolean preRotate(float degrees) {
- native_preRotate(native_instance, degrees);
+ nPreRotate(native_instance, degrees);
return true;
}
/**
- * Preconcats the matrix with the specified skew.
- * M' = M * K(kx, ky, px, py)
+ * Preconcats the matrix with the specified skew. M' = M * K(kx, ky, px, py)
*/
public boolean preSkew(float kx, float ky, float px, float py) {
- native_preSkew(native_instance, kx, ky, px, py);
+ nPreSkew(native_instance, kx, ky, px, py);
return true;
}
/**
- * Preconcats the matrix with the specified skew.
- * M' = M * K(kx, ky)
+ * Preconcats the matrix with the specified skew. M' = M * K(kx, ky)
*/
public boolean preSkew(float kx, float ky) {
- native_preSkew(native_instance, kx, ky);
+ nPreSkew(native_instance, kx, ky);
return true;
}
/**
- * Preconcats the matrix with the specified matrix.
- * M' = M * other
+ * Preconcats the matrix with the specified matrix. M' = M * other
*/
public boolean preConcat(Matrix other) {
- native_preConcat(native_instance, other.native_instance);
+ nPreConcat(native_instance, other.native_instance);
return true;
}
/**
- * Postconcats the matrix with the specified translation.
- * M' = T(dx, dy) * M
+ * Postconcats the matrix with the specified translation. M' = T(dx, dy) * M
*/
public boolean postTranslate(float dx, float dy) {
- native_postTranslate(native_instance, dx, dy);
+ nPostTranslate(native_instance, dx, dy);
return true;
}
/**
- * Postconcats the matrix with the specified scale.
- * M' = S(sx, sy, px, py) * M
+ * Postconcats the matrix with the specified scale. M' = S(sx, sy, px, py) * M
*/
public boolean postScale(float sx, float sy, float px, float py) {
- native_postScale(native_instance, sx, sy, px, py);
+ nPostScale(native_instance, sx, sy, px, py);
return true;
}
/**
- * Postconcats the matrix with the specified scale.
- * M' = S(sx, sy) * M
+ * Postconcats the matrix with the specified scale. M' = S(sx, sy) * M
*/
public boolean postScale(float sx, float sy) {
- native_postScale(native_instance, sx, sy);
+ nPostScale(native_instance, sx, sy);
return true;
}
/**
- * Postconcats the matrix with the specified rotation.
- * M' = R(degrees, px, py) * M
+ * Postconcats the matrix with the specified rotation. M' = R(degrees, px, py) * M
*/
public boolean postRotate(float degrees, float px, float py) {
- native_postRotate(native_instance, degrees, px, py);
+ nPostRotate(native_instance, degrees, px, py);
return true;
}
/**
- * Postconcats the matrix with the specified rotation.
- * M' = R(degrees) * M
+ * Postconcats the matrix with the specified rotation. M' = R(degrees) * M
*/
public boolean postRotate(float degrees) {
- native_postRotate(native_instance, degrees);
+ nPostRotate(native_instance, degrees);
return true;
}
/**
- * Postconcats the matrix with the specified skew.
- * M' = K(kx, ky, px, py) * M
+ * Postconcats the matrix with the specified skew. M' = K(kx, ky, px, py) * M
*/
public boolean postSkew(float kx, float ky, float px, float py) {
- native_postSkew(native_instance, kx, ky, px, py);
+ nPostSkew(native_instance, kx, ky, px, py);
return true;
}
/**
- * Postconcats the matrix with the specified skew.
- * M' = K(kx, ky) * M
+ * Postconcats the matrix with the specified skew. M' = K(kx, ky) * M
*/
public boolean postSkew(float kx, float ky) {
- native_postSkew(native_instance, kx, ky);
+ nPostSkew(native_instance, kx, ky);
return true;
}
/**
- * Postconcats the matrix with the specified matrix.
- * M' = other * M
+ * Postconcats the matrix with the specified matrix. M' = other * M
*/
public boolean postConcat(Matrix other) {
- native_postConcat(native_instance, other.native_instance);
+ nPostConcat(native_instance, other.native_instance);
return true;
}
- /** Controlls how the src rect should align into the dst rect for
- setRectToRect().
- */
+ /**
+ * Controlls how the src rect should align into the dst rect for setRectToRect().
+ */
public enum ScaleToFit {
/**
- * Scale in X and Y independently, so that src matches dst exactly.
- * This may change the aspect ratio of the src.
+ * Scale in X and Y independently, so that src matches dst exactly. This may change the
+ * aspect ratio of the src.
*/
- FILL (0),
+ FILL(0),
/**
- * Compute a scale that will maintain the original src aspect ratio,
- * but will also ensure that src fits entirely inside dst. At least one
- * axis (X or Y) will fit exactly. START aligns the result to the
- * left and top edges of dst.
+ * Compute a scale that will maintain the original src aspect ratio, but will also ensure
+ * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. START
+ * aligns the result to the left and top edges of dst.
*/
- START (1),
+ START(1),
/**
- * Compute a scale that will maintain the original src aspect ratio,
- * but will also ensure that src fits entirely inside dst. At least one
- * axis (X or Y) will fit exactly. The result is centered inside dst.
+ * Compute a scale that will maintain the original src aspect ratio, but will also ensure
+ * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. The
+ * result is centered inside dst.
*/
- CENTER (2),
+ CENTER(2),
/**
- * Compute a scale that will maintain the original src aspect ratio,
- * but will also ensure that src fits entirely inside dst. At least one
- * axis (X or Y) will fit exactly. END aligns the result to the
- * right and bottom edges of dst.
+ * Compute a scale that will maintain the original src aspect ratio, but will also ensure
+ * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. END
+ * aligns the result to the right and bottom edges of dst.
*/
- END (3);
+ END(3);
// the native values must match those in SkMatrix.h
ScaleToFit(int nativeInt) {
this.nativeInt = nativeInt;
}
+
final int nativeInt;
}
/**
- * Set the matrix to the scale and translate values that map the source
- * rectangle to the destination rectangle, returning true if the the result
- * can be represented.
+ * Set the matrix to the scale and translate values that map the source rectangle to the
+ * destination rectangle, returning true if the the result can be represented.
*
* @param src the source rectangle to map from.
* @param dst the destination rectangle to map to.
@@ -572,13 +569,13 @@
if (dst == null || src == null) {
throw new NullPointerException();
}
- return native_setRectToRect(native_instance, src, dst, stf.nativeInt);
+ return nSetRectToRect(native_instance, src, dst, stf.nativeInt);
}
// private helper to perform range checks on arrays of "points"
private static void checkPointArrays(float[] src, int srcIndex,
- float[] dst, int dstIndex,
- int pointCount) {
+ float[] dst, int dstIndex,
+ int pointCount) {
// check for too-small and too-big indices
int srcStop = srcIndex + (pointCount << 1);
int dstStop = dstIndex + (pointCount << 1);
@@ -589,84 +586,81 @@
}
/**
- * Set the matrix such that the specified src points would map to the
- * specified dst points. The "points" are represented as an array of floats,
- * order [x0, y0, x1, y1, ...], where each "point" is 2 float values.
+ * Set the matrix such that the specified src points would map to the specified dst points. The
+ * "points" are represented as an array of floats, order [x0, y0, x1, y1, ...], where each
+ * "point" is 2 float values.
*
- * @param src The array of src [x,y] pairs (points)
+ * @param src The array of src [x,y] pairs (points)
* @param srcIndex Index of the first pair of src values
- * @param dst The array of dst [x,y] pairs (points)
+ * @param dst The array of dst [x,y] pairs (points)
* @param dstIndex Index of the first pair of dst values
* @param pointCount The number of pairs/points to be used. Must be [0..4]
* @return true if the matrix was set to the specified transformation
*/
public boolean setPolyToPoly(float[] src, int srcIndex,
- float[] dst, int dstIndex,
- int pointCount) {
+ float[] dst, int dstIndex,
+ int pointCount) {
if (pointCount > 4) {
throw new IllegalArgumentException();
}
checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
- return native_setPolyToPoly(native_instance, src, srcIndex,
- dst, dstIndex, pointCount);
+ return nSetPolyToPoly(native_instance, src, srcIndex,
+ dst, dstIndex, pointCount);
}
/**
- * If this matrix can be inverted, return true and if inverse is not null,
- * set inverse to be the inverse of this matrix. If this matrix cannot be
- * inverted, ignore inverse and return false.
+ * If this matrix can be inverted, return true and if inverse is not null, set inverse to be the
+ * inverse of this matrix. If this matrix cannot be inverted, ignore inverse and return false.
*/
public boolean invert(Matrix inverse) {
- return native_invert(native_instance, inverse.native_instance);
+ return nInvert(native_instance, inverse.native_instance);
}
/**
- * Apply this matrix to the array of 2D points specified by src, and write
- * the transformed points into the array of points specified by dst. The
- * two arrays represent their "points" as pairs of floats [x, y].
+ * Apply this matrix to the array of 2D points specified by src, and write the transformed
+ * points into the array of points specified by dst. The two arrays represent their "points" as
+ * pairs of floats [x, y].
*
- * @param dst The array of dst points (x,y pairs)
+ * @param dst The array of dst points (x,y pairs)
* @param dstIndex The index of the first [x,y] pair of dst floats
- * @param src The array of src points (x,y pairs)
+ * @param src The array of src points (x,y pairs)
* @param srcIndex The index of the first [x,y] pair of src floats
* @param pointCount The number of points (x,y pairs) to transform
*/
public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
- int pointCount) {
+ int pointCount) {
checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
- native_mapPoints(native_instance, dst, dstIndex, src, srcIndex,
- pointCount, true);
+ nMapPoints(native_instance, dst, dstIndex, src, srcIndex,
+ pointCount, true);
}
/**
- * Apply this matrix to the array of 2D vectors specified by src, and write
- * the transformed vectors into the array of vectors specified by dst. The
- * two arrays represent their "vectors" as pairs of floats [x, y].
+ * Apply this matrix to the array of 2D vectors specified by src, and write the transformed
+ * vectors into the array of vectors specified by dst. The two arrays represent their "vectors"
+ * as pairs of floats [x, y]. Note: this method does not apply the translation associated with
+ * the matrix. Use {@link Matrix#mapPoints(float[], int, float[], int, int)} if you want the
+ * translation to be applied.
*
- * Note: this method does not apply the translation associated with the matrix. Use
- * {@link Matrix#mapPoints(float[], int, float[], int, int)} if you want the translation
- * to be applied.
- *
- * @param dst The array of dst vectors (x,y pairs)
+ * @param dst The array of dst vectors (x,y pairs)
* @param dstIndex The index of the first [x,y] pair of dst floats
- * @param src The array of src vectors (x,y pairs)
+ * @param src The array of src vectors (x,y pairs)
* @param srcIndex The index of the first [x,y] pair of src floats
* @param vectorCount The number of vectors (x,y pairs) to transform
*/
public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,
- int vectorCount) {
+ int vectorCount) {
checkPointArrays(src, srcIndex, dst, dstIndex, vectorCount);
- native_mapPoints(native_instance, dst, dstIndex, src, srcIndex,
- vectorCount, false);
+ nMapPoints(native_instance, dst, dstIndex, src, srcIndex,
+ vectorCount, false);
}
/**
- * Apply this matrix to the array of 2D points specified by src, and write
- * the transformed points into the array of points specified by dst. The
- * two arrays represent their "points" as pairs of floats [x, y].
+ * Apply this matrix to the array of 2D points specified by src, and write the transformed
+ * points into the array of points specified by dst. The two arrays represent their "points" as
+ * pairs of floats [x, y].
*
- * @param dst The array of dst points (x,y pairs)
- * @param src The array of src points (x,y pairs)
+ * @param dst The array of dst points (x,y pairs)
+ * @param src The array of src points (x,y pairs)
*/
public void mapPoints(float[] dst, float[] src) {
if (dst.length != src.length) {
@@ -676,15 +670,14 @@
}
/**
- * Apply this matrix to the array of 2D vectors specified by src, and write
- * the transformed vectors into the array of vectors specified by dst. The
- * two arrays represent their "vectors" as pairs of floats [x, y].
+ * Apply this matrix to the array of 2D vectors specified by src, and write the transformed
+ * vectors into the array of vectors specified by dst. The two arrays represent their "vectors"
+ * as pairs of floats [x, y]. Note: this method does not apply the translation associated with
+ * the matrix. Use {@link Matrix#mapPoints(float[], float[])} if you want the translation to be
+ * applied.
*
- * Note: this method does not apply the translation associated with the matrix. Use
- * {@link Matrix#mapPoints(float[], float[])} if you want the translation to be applied.
- *
- * @param dst The array of dst vectors (x,y pairs)
- * @param src The array of src vectors (x,y pairs)
+ * @param dst The array of dst vectors (x,y pairs)
+ * @param src The array of src vectors (x,y pairs)
*/
public void mapVectors(float[] dst, float[] src) {
if (dst.length != src.length) {
@@ -694,8 +687,8 @@
}
/**
- * Apply this matrix to the array of 2D points, and write the transformed
- * points back into the array
+ * Apply this matrix to the array of 2D points, and write the transformed points back into the
+ * array
*
* @param pts The array [x0, y0, x1, y1, ...] of points to transform.
*/
@@ -704,10 +697,8 @@
}
/**
- * Apply this matrix to the array of 2D vectors, and write the transformed
- * vectors back into the array.
- *
- * Note: this method does not apply the translation associated with the matrix. Use
+ * Apply this matrix to the array of 2D vectors, and write the transformed vectors back into the
+ * array. Note: this method does not apply the translation associated with the matrix. Use
* {@link Matrix#mapPoints(float[])} if you want the translation to be applied.
*
* @param vecs The array [x0, y0, x1, y1, ...] of vectors to transform.
@@ -717,9 +708,9 @@
}
/**
- * Apply this matrix to the src rectangle, and write the transformed
- * rectangle into dst. This is accomplished by transforming the 4 corners of
- * src, and then setting dst to the bounds of those points.
+ * Apply this matrix to the src rectangle, and write the transformed rectangle into dst. This is
+ * accomplished by transforming the 4 corners of src, and then setting dst to the bounds of
+ * those points.
*
* @param dst Where the transformed rectangle is written.
* @param src The original rectangle to be transformed.
@@ -729,13 +720,13 @@
if (dst == null || src == null) {
throw new NullPointerException();
}
- return native_mapRect(native_instance, dst, src);
+ return nMapRect(native_instance, dst, src);
}
/**
- * Apply this matrix to the rectangle, and write the transformed rectangle
- * back into it. This is accomplished by transforming the 4 corners of rect,
- * and then setting it to the bounds of those points
+ * Apply this matrix to the rectangle, and write the transformed rectangle back into it. This is
+ * accomplished by transforming the 4 corners of rect, and then setting it to the bounds of
+ * those points
*
* @param rect The rectangle to transform.
* @return the result of calling rectStaysRect()
@@ -745,34 +736,33 @@
}
/**
- * Return the mean radius of a circle after it has been mapped by
- * this matrix. NOTE: in perspective this value assumes the circle
- * has its center at the origin.
+ * Return the mean radius of a circle after it has been mapped by this matrix. NOTE: in
+ * perspective this value assumes the circle has its center at the origin.
*/
public float mapRadius(float radius) {
- return native_mapRadius(native_instance, radius);
+ return nMapRadius(native_instance, radius);
}
- /** Copy 9 values from the matrix into the array.
- */
+ /**
+ * Copy 9 values from the matrix into the array.
+ */
public void getValues(float[] values) {
if (values.length < 9) {
throw new ArrayIndexOutOfBoundsException();
}
- native_getValues(native_instance, values);
+ nGetValues(native_instance, values);
}
- /** Copy 9 values from the array into the matrix.
- Depending on the implementation of Matrix, these may be
- transformed into 16.16 integers in the Matrix, such that
- a subsequent call to getValues() will not yield exactly
- the same values.
- */
+ /**
+ * Copy 9 values from the array into the matrix. Depending on the implementation of Matrix,
+ * these may be transformed into 16.16 integers in the Matrix, such that a subsequent call to
+ * getValues() will not yield exactly the same values.
+ */
public void setValues(float[] values) {
if (values.length < 9) {
throw new ArrayIndexOutOfBoundsException();
}
- native_setValues(native_instance, values);
+ nSetValues(native_instance, values);
}
@Override
@@ -798,122 +788,156 @@
float[] values = new float[9];
getValues(values);
sb.append('[');
- sb.append(values[0]); sb.append(", "); sb.append(values[1]); sb.append(", ");
- sb.append(values[2]); sb.append("][");
- sb.append(values[3]); sb.append(", "); sb.append(values[4]); sb.append(", ");
- sb.append(values[5]); sb.append("][");
- sb.append(values[6]); sb.append(", "); sb.append(values[7]); sb.append(", ");
- sb.append(values[8]); sb.append(']');
+ sb.append(values[0]);
+ sb.append(", ");
+ sb.append(values[1]);
+ sb.append(", ");
+ sb.append(values[2]);
+ sb.append("][");
+ sb.append(values[3]);
+ sb.append(", ");
+ sb.append(values[4]);
+ sb.append(", ");
+ sb.append(values[5]);
+ sb.append("][");
+ sb.append(values[6]);
+ sb.append(", ");
+ sb.append(values[7]);
+ sb.append(", ");
+ sb.append(values[8]);
+ sb.append(']');
}
/**
* Print short string, to optimize dumping.
+ *
* @hide
*/
public void printShortString(PrintWriter pw) {
float[] values = new float[9];
getValues(values);
pw.print('[');
- pw.print(values[0]); pw.print(", "); pw.print(values[1]); pw.print(", ");
- pw.print(values[2]); pw.print("][");
- pw.print(values[3]); pw.print(", "); pw.print(values[4]); pw.print(", ");
- pw.print(values[5]); pw.print("][");
- pw.print(values[6]); pw.print(", "); pw.print(values[7]); pw.print(", ");
- pw.print(values[8]); pw.print(']');
+ pw.print(values[0]);
+ pw.print(", ");
+ pw.print(values[1]);
+ pw.print(", ");
+ pw.print(values[2]);
+ pw.print("][");
+ pw.print(values[3]);
+ pw.print(", ");
+ pw.print(values[4]);
+ pw.print(", ");
+ pw.print(values[5]);
+ pw.print("][");
+ pw.print(values[6]);
+ pw.print(", ");
+ pw.print(values[7]);
+ pw.print(", ");
+ pw.print(values[8]);
+ pw.print(']');
}
- @Override
- protected void finalize() throws Throwable {
- try {
- finalizer(native_instance);
- native_instance = 0; // Other finalizers can still call us.
- } finally {
- super.finalize();
- }
- }
-
- /*package*/ final long ni() {
+ /** @hide */
+ public final long ni() {
return native_instance;
}
- private static native long native_create(long native_src_or_zero);
- private static native boolean native_isIdentity(long native_object);
- private static native boolean native_isAffine(long native_object);
- private static native boolean native_rectStaysRect(long native_object);
- private static native void native_reset(long native_object);
- private static native void native_set(long native_object,
- long native_other);
- private static native void native_setTranslate(long native_object,
- float dx, float dy);
- private static native void native_setScale(long native_object,
- float sx, float sy, float px, float py);
- private static native void native_setScale(long native_object,
- float sx, float sy);
- private static native void native_setRotate(long native_object,
- float degrees, float px, float py);
- private static native void native_setRotate(long native_object,
- float degrees);
- private static native void native_setSinCos(long native_object,
- float sinValue, float cosValue, float px, float py);
- private static native void native_setSinCos(long native_object,
- float sinValue, float cosValue);
- private static native void native_setSkew(long native_object,
- float kx, float ky, float px, float py);
- private static native void native_setSkew(long native_object,
- float kx, float ky);
- private static native void native_setConcat(long native_object,
- long native_a,
- long native_b);
- private static native void native_preTranslate(long native_object,
- float dx, float dy);
- private static native void native_preScale(long native_object,
- float sx, float sy, float px, float py);
- private static native void native_preScale(long native_object,
- float sx, float sy);
- private static native void native_preRotate(long native_object,
- float degrees, float px, float py);
- private static native void native_preRotate(long native_object,
- float degrees);
- private static native void native_preSkew(long native_object,
- float kx, float ky, float px, float py);
- private static native void native_preSkew(long native_object,
- float kx, float ky);
- private static native void native_preConcat(long native_object,
- long native_other_matrix);
- private static native void native_postTranslate(long native_object,
- float dx, float dy);
- private static native void native_postScale(long native_object,
- float sx, float sy, float px, float py);
- private static native void native_postScale(long native_object,
- float sx, float sy);
- private static native void native_postRotate(long native_object,
- float degrees, float px, float py);
- private static native void native_postRotate(long native_object,
- float degrees);
- private static native void native_postSkew(long native_object,
- float kx, float ky, float px, float py);
- private static native void native_postSkew(long native_object,
- float kx, float ky);
- private static native void native_postConcat(long native_object,
- long native_other_matrix);
- private static native boolean native_setRectToRect(long native_object,
- RectF src, RectF dst, int stf);
- private static native boolean native_setPolyToPoly(long native_object,
- float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount);
- private static native boolean native_invert(long native_object,
- long native_inverse);
- private static native void native_mapPoints(long native_object,
- float[] dst, int dstIndex, float[] src, int srcIndex,
- int ptCount, boolean isPts);
- private static native boolean native_mapRect(long native_object,
- RectF dst, RectF src);
- private static native float native_mapRadius(long native_object,
- float radius);
- private static native void native_getValues(long native_object,
- float[] values);
- private static native void native_setValues(long native_object,
- float[] values);
- private static native boolean native_equals(long native_a, long native_b);
- private static native void finalizer(long native_instance);
+ // ------------------ Regular JNI ------------------------
+
+ private static native long nCreate(long nSrc_or_zero);
+ private static native long nGetNativeFinalizer();
+
+
+ // ------------------ Fast JNI ------------------------
+
+ @FastNative
+ private static native boolean nSetRectToRect(long nObject,
+ RectF src, RectF dst, int stf);
+ @FastNative
+ private static native boolean nSetPolyToPoly(long nObject,
+ float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount);
+ @FastNative
+ private static native void nMapPoints(long nObject,
+ float[] dst, int dstIndex, float[] src, int srcIndex,
+ int ptCount, boolean isPts);
+ @FastNative
+ private static native boolean nMapRect(long nObject, RectF dst, RectF src);
+ @FastNative
+ private static native void nGetValues(long nObject, float[] values);
+ @FastNative
+ private static native void nSetValues(long nObject, float[] values);
+
+
+ // ------------------ Critical JNI ------------------------
+
+ @CriticalNative
+ private static native boolean nIsIdentity(long nObject);
+ @CriticalNative
+ private static native boolean nIsAffine(long nObject);
+ @CriticalNative
+ private static native boolean nRectStaysRect(long nObject);
+ @CriticalNative
+ private static native void nReset(long nObject);
+ @CriticalNative
+ private static native void nSet(long nObject, long nOther);
+ @CriticalNative
+ private static native void nSetTranslate(long nObject, float dx, float dy);
+ @CriticalNative
+ private static native void nSetScale(long nObject, float sx, float sy, float px, float py);
+ @CriticalNative
+ private static native void nSetScale(long nObject, float sx, float sy);
+ @CriticalNative
+ private static native void nSetRotate(long nObject, float degrees, float px, float py);
+ @CriticalNative
+ private static native void nSetRotate(long nObject, float degrees);
+ @CriticalNative
+ private static native void nSetSinCos(long nObject, float sinValue, float cosValue,
+ float px, float py);
+ @CriticalNative
+ private static native void nSetSinCos(long nObject, float sinValue, float cosValue);
+ @CriticalNative
+ private static native void nSetSkew(long nObject, float kx, float ky, float px, float py);
+ @CriticalNative
+ private static native void nSetSkew(long nObject, float kx, float ky);
+ @CriticalNative
+ private static native void nSetConcat(long nObject, long nA, long nB);
+ @CriticalNative
+ private static native void nPreTranslate(long nObject, float dx, float dy);
+ @CriticalNative
+ private static native void nPreScale(long nObject, float sx, float sy, float px, float py);
+ @CriticalNative
+ private static native void nPreScale(long nObject, float sx, float sy);
+ @CriticalNative
+ private static native void nPreRotate(long nObject, float degrees, float px, float py);
+ @CriticalNative
+ private static native void nPreRotate(long nObject, float degrees);
+ @CriticalNative
+ private static native void nPreSkew(long nObject, float kx, float ky, float px, float py);
+ @CriticalNative
+ private static native void nPreSkew(long nObject, float kx, float ky);
+ @CriticalNative
+ private static native void nPreConcat(long nObject, long nOther_matrix);
+ @CriticalNative
+ private static native void nPostTranslate(long nObject, float dx, float dy);
+ @CriticalNative
+ private static native void nPostScale(long nObject, float sx, float sy, float px, float py);
+ @CriticalNative
+ private static native void nPostScale(long nObject, float sx, float sy);
+ @CriticalNative
+ private static native void nPostRotate(long nObject, float degrees, float px, float py);
+ @CriticalNative
+ private static native void nPostRotate(long nObject, float degrees);
+ @CriticalNative
+ private static native void nPostSkew(long nObject, float kx, float ky, float px, float py);
+ @CriticalNative
+ private static native void nPostSkew(long nObject, float kx, float ky);
+ @CriticalNative
+ private static native void nPostConcat(long nObject, long nOther_matrix);
+ @CriticalNative
+ private static native boolean nInvert(long nObject, long nInverse);
+ @CriticalNative
+ private static native float nMapRadius(long nObject, float radius);
+ @CriticalNative
+ private static native boolean nEquals(long nA, long nB);
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 4abe50f..98d45dc 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -27,6 +27,9 @@
import com.android.internal.annotations.GuardedBy;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
import java.util.HashMap;
import java.util.Locale;
@@ -604,8 +607,6 @@
return nGetFlags(mNativePaint);
}
- private native int nGetFlags(long paintPtr);
-
/**
* Set the paint's flags. Use the Flag enum to specific flag values.
*
@@ -615,8 +616,6 @@
nSetFlags(mNativePaint, flags);
}
- private native void nSetFlags(long paintPtr, int flags);
-
/**
* Return the paint's hinting mode. Returns either
* {@link #HINTING_OFF} or {@link #HINTING_ON}.
@@ -625,8 +624,6 @@
return nGetHinting(mNativePaint);
}
- private native int nGetHinting(long paintPtr);
-
/**
* Set the paint's hinting mode. May be either
* {@link #HINTING_OFF} or {@link #HINTING_ON}.
@@ -635,8 +632,6 @@
nSetHinting(mNativePaint, mode);
}
- private native void nSetHinting(long paintPtr, int mode);
-
/**
* Helper for getFlags(), returning true if ANTI_ALIAS_FLAG bit is set
* AntiAliasing smooths out the edges of what is being drawn, but is has
@@ -661,8 +656,6 @@
nSetAntiAlias(mNativePaint, aa);
}
- private native void nSetAntiAlias(long paintPtr, boolean aa);
-
/**
* Helper for getFlags(), returning true if DITHER_FLAG bit is set
* Dithering affects how colors that are higher precision than the device
@@ -691,8 +684,6 @@
nSetDither(mNativePaint, dither);
}
- private native void nSetDither(long paintPtr, boolean dither);
-
/**
* Helper for getFlags(), returning true if LINEAR_TEXT_FLAG bit is set
*
@@ -712,8 +703,6 @@
nSetLinearText(mNativePaint, linearText);
}
- private native void nSetLinearText(long paintPtr, boolean linearText);
-
/**
* Helper for getFlags(), returning true if SUBPIXEL_TEXT_FLAG bit is set
*
@@ -733,8 +722,6 @@
nSetSubpixelText(mNativePaint, subpixelText);
}
- private native void nSetSubpixelText(long paintPtr, boolean subpixelText);
-
/**
* Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set
*
@@ -754,8 +741,6 @@
nSetUnderlineText(mNativePaint, underlineText);
}
- private native void nSetUnderlineText(long paintPtr, boolean underlineText);
-
/**
* Helper for getFlags(), returning true if STRIKE_THRU_TEXT_FLAG bit is set
*
@@ -775,8 +760,6 @@
nSetStrikeThruText(mNativePaint, strikeThruText);
}
- private native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
-
/**
* Helper for getFlags(), returning true if FAKE_BOLD_TEXT_FLAG bit is set
*
@@ -796,8 +779,6 @@
nSetFakeBoldText(mNativePaint, fakeBoldText);
}
- private native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);
-
/**
* Whether or not the bitmap filter is activated.
* Filtering affects the sampling of bitmaps when they are transformed.
@@ -823,8 +804,6 @@
nSetFilterBitmap(mNativePaint, filter);
}
- private native void nSetFilterBitmap(long paintPtr, boolean filter);
-
/**
* Return the paint's style, used for controlling how primitives'
* geometries are interpreted (except for drawBitmap, which always assumes
@@ -860,8 +839,6 @@
return nGetColor(mNativePaint);
}
- private native int nGetColor(long paintPtr);
-
/**
* Set the paint's color. Note that the color is an int containing alpha
* as well as r,g,b. This 32bit value is not premultiplied, meaning that
@@ -874,8 +851,6 @@
nSetColor(mNativePaint, color);
}
- private native void nSetColor(long paintPtr, @ColorInt int color);
-
/**
* Helper to getColor() that just returns the color's alpha value. This is
* the same as calling getColor() >>> 24. It always returns a value between
@@ -887,8 +862,6 @@
return nGetAlpha(mNativePaint);
}
- private native int nGetAlpha(long paintPtr);
-
/**
* Helper to setColor(), that only assigns the color's alpha value,
* leaving its r,g,b values unchanged. Results are undefined if the alpha
@@ -900,8 +873,6 @@
nSetAlpha(mNativePaint, a);
}
- private native void nSetAlpha(long paintPtr, int a);
-
/**
* Helper to setColor(), that takes a,r,g,b and constructs the color int
*
@@ -927,8 +898,6 @@
return nGetStrokeWidth(mNativePaint);
}
- private native float nGetStrokeWidth(long paintPtr);
-
/**
* Set the width for stroking.
* Pass 0 to stroke in hairline mode.
@@ -941,8 +910,6 @@
nSetStrokeWidth(mNativePaint, width);
}
- private native void nSetStrokeWidth(long paintPtr, float width);
-
/**
* Return the paint's stroke miter value. Used to control the behavior
* of miter joins when the joins angle is sharp.
@@ -954,8 +921,6 @@
return nGetStrokeMiter(mNativePaint);
}
- private native float nGetStrokeMiter(long paintPtr);
-
/**
* Set the paint's stroke miter value. This is used to control the behavior
* of miter joins when the joins angle is sharp. This value must be >= 0.
@@ -967,8 +932,6 @@
nSetStrokeMiter(mNativePaint, miter);
}
- private native void nSetStrokeMiter(long paintPtr, float miter);
-
/**
* Return the paint's Cap, controlling how the start and end of stroked
* lines and paths are treated.
@@ -1387,8 +1350,6 @@
return nIsElegantTextHeight(mNativePaint);
}
- private native boolean nIsElegantTextHeight(long paintPtr);
-
/**
* Set the paint's elegant height metrics flag. This setting selects font
* variants that have not been compacted to fit Latin-based vertical
@@ -1400,8 +1361,6 @@
nSetElegantTextHeight(mNativePaint, elegant);
}
- private native void nSetElegantTextHeight(long paintPtr, boolean elegant);
-
/**
* Return the paint's text size.
*
@@ -1411,8 +1370,6 @@
return nGetTextSize(mNativePaint);
}
- private native float nGetTextSize(long paintPtr);
-
/**
* Set the paint's text size. This value must be > 0
*
@@ -1422,8 +1379,6 @@
nSetTextSize(mNativePaint, textSize);
}
- private native void nSetTextSize(long paintPtr, float textSize);
-
/**
* Return the paint's horizontal scale factor for text. The default value
* is 1.0.
@@ -1434,8 +1389,6 @@
return nGetTextScaleX(mNativePaint);
}
- private native float nGetTextScaleX(long paintPtr);
-
/**
* Set the paint's horizontal scale factor for text. The default value
* is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will
@@ -1447,8 +1400,6 @@
nSetTextScaleX(mNativePaint, scaleX);
}
- private native void nSetTextScaleX(long paintPtr, float scaleX);
-
/**
* Return the paint's horizontal skew factor for text. The default value
* is 0.
@@ -1459,8 +1410,6 @@
return nGetTextSkewX(mNativePaint);
}
- private native float nGetTextSkewX(long paintPtr);
-
/**
* Set the paint's horizontal skew factor for text. The default value
* is 0. For approximating oblique text, use values around -0.25.
@@ -1471,8 +1420,6 @@
nSetTextSkewX(mNativePaint, skewX);
}
- private native void nSetTextSkewX(long paintPtr, float skewX);
-
/**
* Return the paint's letter-spacing for text. The default value
* is 0.
@@ -1565,8 +1512,6 @@
return nAscent(mNativePaint, mNativeTypeface);
}
- private native float nAscent(long paintPtr, long typefacePtr);
-
/**
* Return the distance below (positive) the baseline (descent) based on the
* current typeface and text size.
@@ -1578,8 +1523,6 @@
return nDescent(mNativePaint, mNativeTypeface);
}
- private native float nDescent(long paintPtr, long typefacePtr);
-
/**
* Class that describes the various metrics for a font at a given text size.
* Remember, Y values increase going down, so those values will be positive,
@@ -1624,9 +1567,6 @@
return nGetFontMetrics(mNativePaint, mNativeTypeface, metrics);
}
- private native float nGetFontMetrics(long paintPtr,
- long typefacePtr, FontMetrics metrics);
-
/**
* Allocates a new FontMetrics object, and then calls getFontMetrics(fm)
* with it, returning the object.
@@ -1686,9 +1626,6 @@
return nGetFontMetricsInt(mNativePaint, mNativeTypeface, fmi);
}
- private native int nGetFontMetricsInt(long paintPtr,
- long typefacePtr, FontMetricsInt fmi);
-
public FontMetricsInt getFontMetricsInt() {
FontMetricsInt fm = new FontMetricsInt();
getFontMetricsInt(fm);
@@ -1860,10 +1797,6 @@
return res;
}
- private static native int nBreakText(long nObject, long nTypeface,
- char[] text, int index, int count,
- float maxWidth, int bidiFlags, float[] measuredWidth);
-
/**
* Measure the text, stopping early if the measured width exceeds maxWidth.
* Return the number of chars that were measured, and if measuredWidth is
@@ -1952,10 +1885,6 @@
return res;
}
- private static native int nBreakText(long nObject, long nTypeface,
- String text, boolean measureForwards,
- float maxWidth, int bidiFlags, float[] measuredWidth);
-
/**
* Return the advance widths for the characters in the string.
*
@@ -2679,73 +2608,34 @@
return result;
}
+ // regular JNI
+ private static native long nGetNativeFinalizer();
private static native long nInit();
private static native long nInitWithPaint(long paint);
- private static native void nReset(long paintPtr);
- private static native void nSet(long paintPtrDest, long paintPtrSrc);
- private static native int nGetStyle(long paintPtr);
- private static native void nSetStyle(long paintPtr, int style);
- private static native int nGetStrokeCap(long paintPtr);
- private static native void nSetStrokeCap(long paintPtr, int cap);
- private static native int nGetStrokeJoin(long paintPtr);
- private static native void nSetStrokeJoin(long paintPtr,
- int join);
- private static native boolean nGetFillPath(long paintPtr,
- long src, long dst);
- private static native long nSetShader(long paintPtr, long shader);
- private static native long nSetColorFilter(long paintPtr,
- long filter);
- private static native void nSetXfermode(long paintPtr, int xfermode);
- private static native long nSetPathEffect(long paintPtr,
- long effect);
- private static native long nSetMaskFilter(long paintPtr,
- long maskfilter);
- private static native long nSetTypeface(long paintPtr,
- long typeface);
- private static native long nSetRasterizer(long paintPtr,
- long rasterizer);
-
- private static native int nGetTextAlign(long paintPtr);
- private static native void nSetTextAlign(long paintPtr,
- int align);
-
- private static native int nSetTextLocales(long paintPtr, String locales);
- private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
- int mMinikinLangListId);
-
+ private static native int nBreakText(long nObject, long nTypeface,
+ char[] text, int index, int count,
+ float maxWidth, int bidiFlags, float[] measuredWidth);
+ private static native int nBreakText(long nObject, long nTypeface,
+ String text, boolean measureForwards,
+ float maxWidth, int bidiFlags, float[] measuredWidth);
private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
char[] text, int index, int count, int contextIndex, int contextCount,
int bidiFlags, float[] advances, int advancesIndex);
private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
String text, int start, int end, int contextStart, int contextEnd,
int bidiFlags, float[] advances, int advancesIndex);
-
private native int nGetTextRunCursor(long paintPtr, char[] text,
int contextStart, int contextLength, int dir, int offset, int cursorOpt);
private native int nGetTextRunCursor(long paintPtr, String text,
int contextStart, int contextEnd, int dir, int offset, int cursorOpt);
-
private static native void nGetTextPath(long paintPtr, long typefacePtr,
int bidiFlags, char[] text, int index, int count, float x, float y, long path);
private static native void nGetTextPath(long paintPtr, long typefacePtr,
int bidiFlags, String text, int start, int end, float x, float y, long path);
private static native void nGetStringBounds(long nativePaint, long typefacePtr,
- String text, int start, int end, int bidiFlags, Rect bounds);
+ String text, int start, int end, int bidiFlags, Rect bounds);
private static native void nGetCharArrayBounds(long nativePaint, long typefacePtr,
- char[] text, int index, int count, int bidiFlags, Rect bounds);
- private static native long nGetNativeFinalizer();
-
- private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, int color);
- private static native boolean nHasShadowLayer(long paintPtr);
-
- private static native float nGetLetterSpacing(long paintPtr);
- private static native void nSetLetterSpacing(long paintPtr,
- float letterSpacing);
- private static native void nSetFontFeatureSettings(long paintPtr,
- String settings);
- private static native int nGetHyphenEdit(long paintPtr);
- private static native void nSetHyphenEdit(long paintPtr, int hyphen);
+ char[] text, int index, int count, int bidiFlags, Rect bounds);
private static native boolean nHasGlyph(long paintPtr, long typefacePtr,
int bidiFlags, String string);
private static native float nGetRunAdvance(long paintPtr, long typefacePtr,
@@ -2754,4 +2644,134 @@
private static native int nGetOffsetForAdvance(long paintPtr,
long typefacePtr, char[] text, int start, int end, int contextStart, int contextEnd,
boolean isRtl, float advance);
+
+
+ // ---------------- @FastNative ------------------------
+
+ @FastNative
+ private static native int nSetTextLocales(long paintPtr, String locales);
+ @FastNative
+ private static native void nSetFontFeatureSettings(long paintPtr, String settings);
+ @FastNative
+ private static native float nGetFontMetrics(long paintPtr,
+ long typefacePtr, FontMetrics metrics);
+ @FastNative
+ private static native int nGetFontMetricsInt(long paintPtr,
+ long typefacePtr, FontMetricsInt fmi);
+
+
+ // ---------------- @CriticalNative ------------------------
+
+ @CriticalNative
+ private static native void nReset(long paintPtr);
+ @CriticalNative
+ private static native void nSet(long paintPtrDest, long paintPtrSrc);
+ @CriticalNative
+ private static native int nGetStyle(long paintPtr);
+ @CriticalNative
+ private static native void nSetStyle(long paintPtr, int style);
+ @CriticalNative
+ private static native int nGetStrokeCap(long paintPtr);
+ @CriticalNative
+ private static native void nSetStrokeCap(long paintPtr, int cap);
+ @CriticalNative
+ private static native int nGetStrokeJoin(long paintPtr);
+ @CriticalNative
+ private static native void nSetStrokeJoin(long paintPtr, int join);
+ @CriticalNative
+ private static native boolean nGetFillPath(long paintPtr, long src, long dst);
+ @CriticalNative
+ private static native long nSetShader(long paintPtr, long shader);
+ @CriticalNative
+ private static native long nSetColorFilter(long paintPtr, long filter);
+ @CriticalNative
+ private static native void nSetXfermode(long paintPtr, int xfermode);
+ @CriticalNative
+ private static native long nSetPathEffect(long paintPtr, long effect);
+ @CriticalNative
+ private static native long nSetMaskFilter(long paintPtr, long maskfilter);
+ @CriticalNative
+ private static native long nSetTypeface(long paintPtr, long typeface);
+ @CriticalNative
+ private static native long nSetRasterizer(long paintPtr, long rasterizer);
+ @CriticalNative
+ private static native int nGetTextAlign(long paintPtr);
+ @CriticalNative
+ private static native void nSetTextAlign(long paintPtr, int align);
+ @CriticalNative
+ private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
+ int mMinikinLangListId);
+ @CriticalNative
+ private static native void nSetShadowLayer(long paintPtr,
+ float radius, float dx, float dy, int color);
+ @CriticalNative
+ private static native boolean nHasShadowLayer(long paintPtr);
+ @CriticalNative
+ private static native float nGetLetterSpacing(long paintPtr);
+ @CriticalNative
+ private static native void nSetLetterSpacing(long paintPtr, float letterSpacing);
+ @CriticalNative
+ private static native int nGetHyphenEdit(long paintPtr);
+ @CriticalNative
+ private static native void nSetHyphenEdit(long paintPtr, int hyphen);
+ @CriticalNative
+ private static native void nSetStrokeMiter(long paintPtr, float miter);
+ @CriticalNative
+ private static native float nGetStrokeMiter(long paintPtr);
+ @CriticalNative
+ private static native void nSetStrokeWidth(long paintPtr, float width);
+ @CriticalNative
+ private static native float nGetStrokeWidth(long paintPtr);
+ @CriticalNative
+ private static native void nSetAlpha(long paintPtr, int a);
+ @CriticalNative
+ private static native void nSetDither(long paintPtr, boolean dither);
+ @CriticalNative
+ private static native int nGetFlags(long paintPtr);
+ @CriticalNative
+ private static native void nSetFlags(long paintPtr, int flags);
+ @CriticalNative
+ private static native int nGetHinting(long paintPtr);
+ @CriticalNative
+ private static native void nSetHinting(long paintPtr, int mode);
+ @CriticalNative
+ private static native void nSetAntiAlias(long paintPtr, boolean aa);
+ @CriticalNative
+ private static native void nSetLinearText(long paintPtr, boolean linearText);
+ @CriticalNative
+ private static native void nSetSubpixelText(long paintPtr, boolean subpixelText);
+ @CriticalNative
+ private static native void nSetUnderlineText(long paintPtr, boolean underlineText);
+ @CriticalNative
+ private static native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);
+ @CriticalNative
+ private static native void nSetFilterBitmap(long paintPtr, boolean filter);
+ @CriticalNative
+ private static native int nGetColor(long paintPtr);
+ @CriticalNative
+ private static native void nSetColor(long paintPtr, @ColorInt int color);
+ @CriticalNative
+ private static native int nGetAlpha(long paintPtr);
+ @CriticalNative
+ private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
+ @CriticalNative
+ private static native boolean nIsElegantTextHeight(long paintPtr);
+ @CriticalNative
+ private static native void nSetElegantTextHeight(long paintPtr, boolean elegant);
+ @CriticalNative
+ private static native float nGetTextSize(long paintPtr);
+ @CriticalNative
+ private static native float nGetTextScaleX(long paintPtr);
+ @CriticalNative
+ private static native void nSetTextScaleX(long paintPtr, float scaleX);
+ @CriticalNative
+ private static native float nGetTextSkewX(long paintPtr);
+ @CriticalNative
+ private static native void nSetTextSkewX(long paintPtr, float skewX);
+ @CriticalNative
+ private static native float nAscent(long paintPtr, long typefacePtr);
+ @CriticalNative
+ private static native float nDescent(long paintPtr, long typefacePtr);
+ @CriticalNative
+ private static native void nSetTextSize(long paintPtr, float textSize);
}
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 2294b86..4ed2581 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -775,7 +775,8 @@
}
}
- final long readOnlyNI() {
+ /** @hide */
+ public final long readOnlyNI() {
return mNativePath;
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index dcca431..40a2833 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -57,6 +57,9 @@
import com.android.internal.R;
import com.android.internal.util.VirtualRefBasePtr;
+
+import dalvik.annotation.optimization.FastNative;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -455,7 +458,11 @@
int eventType = parser.getEventType();
float pathErrorScale = 1;
- while (eventType != XmlPullParser.END_DOCUMENT) {
+ final int innerDepth = parser.getDepth() + 1;
+
+ // Parse everything until the end of the animated-vector element.
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
if (ANIMATED_VECTOR.equals(tagName)) {
@@ -1719,22 +1726,30 @@
private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
long nativeInterpolator, long startDelay, long duration, int repeatCount,
int repeatMode);
-
- private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
- float startValue, float endValue);
-
- private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
- long endValuePtr);
- private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
- int startValue, int endValue);
- private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
- float startValue, float endValue);
- private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
- float endValue);
private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length);
private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
+
+ // ------------- @FastNative -------------------
+
+ @FastNative
+ private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue);
+ @FastNative
+ private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+ long endValuePtr);
+ @FastNative
+ private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+ int startValue, int endValue);
+ @FastNative
+ private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue);
+ @FastNative
+ private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+ float endValue);
+ @FastNative
private static native void nEnd(long animatorSetPtr);
+ @FastNative
private static native void nReset(long animatorSetPtr);
}
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 9d8ede0..6deeb0d 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -49,7 +49,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.Collection;
/**
* A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
@@ -463,31 +462,14 @@
return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
}
- private void updateMirrorMatrix(float dx) {
- if (mMirrorMatrix == null) {
- mMirrorMatrix = new Matrix();
- }
- mMirrorMatrix.setTranslate(dx, 0);
- mMirrorMatrix.preScale(-1.0f, 1.0f);
- }
-
@Override
protected void onBoundsChange(Rect bounds) {
mDstRectAndInsetsDirty = true;
+ final Bitmap bitmap = mBitmapState.mBitmap;
final Shader shader = mBitmapState.mPaint.getShader();
- if (shader != null) {
- if (needMirroring()) {
- updateMirrorMatrix(bounds.right - bounds.left);
- shader.setLocalMatrix(mMirrorMatrix);
- mBitmapState.mPaint.setShader(shader);
- } else {
- if (mMirrorMatrix != null) {
- mMirrorMatrix = null;
- shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
- mBitmapState.mPaint.setShader(shader);
- }
- }
+ if (bitmap != null && shader != null) {
+ updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring());
}
}
@@ -548,19 +530,7 @@
canvas.restore();
}
} else {
- if (needMirroring) {
- // Mirror the bitmap
- updateMirrorMatrix(mDstRect.right - mDstRect.left);
- shader.setLocalMatrix(mMirrorMatrix);
- paint.setShader(shader);
- } else {
- if (mMirrorMatrix != null) {
- mMirrorMatrix = null;
- shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
- paint.setShader(shader);
- }
- }
-
+ updateShaderMatrix(bitmap, paint, shader, needMirroring);
canvas.drawRect(mDstRect, paint);
}
@@ -573,6 +543,51 @@
}
}
+ /**
+ * Updates the {@code paint}'s shader matrix to be consistent with the
+ * destination size and layout direction.
+ *
+ * @param bitmap the bitmap to be drawn
+ * @param paint the paint used to draw the bitmap
+ * @param shader the shader to set on the paint
+ * @param needMirroring whether the bitmap should be mirrored
+ */
+ private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint,
+ @NonNull Shader shader, boolean needMirroring) {
+ final int sourceDensity = bitmap.getDensity();
+ final int targetDensity = mTargetDensity;
+ final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity;
+ if (needScaling || needMirroring) {
+ final Matrix matrix = getOrCreateMirrorMatrix();
+ matrix.reset();
+
+ if (needMirroring) {
+ final int dx = mDstRect.right - mDstRect.left;
+ matrix.setTranslate(dx, 0);
+ matrix.setScale(-1, 1);
+ }
+
+ if (needScaling) {
+ final float densityScale = targetDensity / (float) sourceDensity;
+ matrix.postScale(densityScale, densityScale);
+ }
+
+ shader.setLocalMatrix(matrix);
+ } else {
+ mMirrorMatrix = null;
+ shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+ }
+
+ paint.setShader(shader);
+ }
+
+ private Matrix getOrCreateMirrorMatrix() {
+ if (mMirrorMatrix == null) {
+ mMirrorMatrix = new Matrix();
+ }
+ return mMirrorMatrix;
+ }
+
private void updateDstRectAndInsetsIfDirty() {
if (mDstRectAndInsetsDirty) {
if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
@@ -941,14 +956,6 @@
}
@Override
- public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
- if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) {
- return mBitmap.getWidth() * mBitmap.getHeight();
- }
- return 0;
- }
-
- @Override
public Drawable newDrawable() {
return new BitmapDrawable(this, null);
}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index af20f8a..6ddc2d7 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -58,7 +58,6 @@
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Arrays;
-import java.util.Collection;
/**
* A Drawable is a general abstraction for "something that can be drawn." Most
@@ -109,6 +108,9 @@
* <li> <b>Nine Patch</b>: an extension to the PNG format allows it to
* specify information about how to stretch it and place things inside of
* it.
+ * <li><b>Vector</b>: a drawable defined in an XML file as a set of points,
+ * lines, and curves along with its associated color information. This type
+ * of drawable can be scaled without loss of display quality.
* <li> <b>Shape</b>: contains simple drawing commands instead of a raw
* bitmap, allowing it to resize better in some cases.
* <li> <b>Layers</b>: a compound drawable, which draws multiple underlying
@@ -1364,19 +1366,6 @@
public abstract @Config int getChangingConfigurations();
/**
- * @return Total pixel count
- * @hide
- */
- public int addAtlasableBitmaps(@NonNull Collection<Bitmap> atlasList) {
- return 0;
- }
-
- /** @hide */
- protected final boolean isAtlasable(@Nullable Bitmap bitmap) {
- return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888;
- }
-
- /**
* Return whether this constant state can have a theme applied.
*/
public boolean canApplyTheme() {
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index c7a3c75..abdc2b9 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -21,7 +21,6 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
@@ -35,8 +34,6 @@
import android.util.SparseArray;
import android.view.View;
-import java.util.Collection;
-
/**
* A helper class that contains several {@link Drawable}s and selects which one to use.
*
@@ -1194,19 +1191,6 @@
return true;
}
- /** @hide */
- @Override
- public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
- final int N = mNumChildren;
- int pixelCount = 0;
- for (int i = 0; i < N; i++) {
- final ConstantState state = getChild(i).getConstantState();
- if (state != null) {
- pixelCount += state.addAtlasableBitmaps(atlasList);
- }
- }
- return pixelCount;
- }
}
protected void setConstantState(DrawableContainerState state) {
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 5abfc54..5887939 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -28,7 +28,6 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
@@ -41,7 +40,6 @@
import android.view.View;
import java.io.IOException;
-import java.util.Collection;
/**
* Drawable container with only one child element.
@@ -508,15 +506,6 @@
}
@Override
- public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
- final Drawable.ConstantState state = mDrawableState;
- if (state != null) {
- return state.addAtlasableBitmaps(atlasList);
- }
- return 0;
- }
-
- @Override
public Drawable newDrawable() {
return newDrawable(null);
}
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index c30c4c2..e09fea5 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -23,7 +23,6 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
@@ -42,7 +41,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.Collection;
/**
* A Drawable that manages an array of other Drawables. These are drawn in array
@@ -2128,22 +2126,6 @@
mHaveIsStateful = false;
}
- @Override
- public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
- final ChildDrawable[] array = mChildren;
- final int N = mNum;
- int pixelCount = 0;
- for (int i = 0; i < N; i++) {
- final Drawable dr = array[i].mDrawable;
- if (dr != null) {
- final ConstantState state = dr.getConstantState();
- if (state != null) {
- pixelCount += state.addAtlasableBitmaps(atlasList);
- }
- }
- }
- return pixelCount;
- }
}
}
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index d962385..c7183d9 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -49,7 +49,6 @@
import java.io.IOException;
import java.io.InputStream;
-import java.util.Collection;
/**
*
@@ -633,15 +632,6 @@
}
@Override
- public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
- final Bitmap bitmap = mNinePatch.getBitmap();
- if (isAtlasable(bitmap) && atlasList.add(bitmap)) {
- return bitmap.getWidth() * bitmap.getHeight();
- }
- return 0;
- }
-
- @Override
public Drawable newDrawable() {
return new NinePatchDrawable(this, null);
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 9ff6965..e83104d 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -55,6 +55,7 @@
import java.util.HashMap;
import java.util.Stack;
+import dalvik.annotation.optimization.FastNative;
import dalvik.system.VMRuntime;
/**
@@ -76,27 +77,36 @@
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of this vector drawable.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:width</code></dt>
* <dd>Used to define the intrinsic width of the drawable.
* This support all the dimension units, normally specified with dp.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:height</code></dt>
* <dd>Used to define the intrinsic height the drawable.
* This support all the dimension units, normally specified with dp.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:viewportWidth</code></dt>
* <dd>Used to define the width of the viewport space. Viewport is basically
* the virtual canvas where the paths are drawn on.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:viewportHeight</code></dt>
* <dd>Used to define the height of the viewport space. Viewport is basically
* the virtual canvas where the paths are drawn on.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:tint</code></dt>
* <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:tintMode</code></dt>
* <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:autoMirrored</code></dt>
* <dd>Indicates if the drawable needs to be mirrored when its layout direction is
* RTL (right-to-left).</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:alpha</code></dt>
* <dd>The opacity of this drawable.</dd>
+ * <dd>Animatable : Yes.</dd>
* </dl></dd>
* </dl>
*
@@ -108,24 +118,32 @@
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of the group.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:rotation</code></dt>
* <dd>The degrees of rotation of the group.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:pivotX</code></dt>
* <dd>The X coordinate of the pivot for the scale and rotation of the group.
* This is defined in the viewport space.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:pivotY</code></dt>
* <dd>The Y coordinate of the pivot for the scale and rotation of the group.
* This is defined in the viewport space.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:scaleX</code></dt>
* <dd>The amount of scale on the X Coordinate.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:scaleY</code></dt>
* <dd>The amount of scale on the Y coordinate.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:translateX</code></dt>
* <dd>The amount of translation on the X coordinate.
* This is defined in the viewport space.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:translateY</code></dt>
* <dd>The amount of translation on the Y coordinate.
* This is defined in the viewport space.</dd>
+ * <dd>Animatable : Yes.</dd>
* </dl></dd>
* </dl>
*
@@ -135,45 +153,60 @@
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of the path.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:pathData</code></dt>
* <dd>Defines path data using exactly same format as "d" attribute
* in the SVG's path data. This is defined in the viewport space.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:fillColor</code></dt>
* <dd>Specifies the color used to fill the path. May be a color or, for SDK 24+, a color state list
* or a gradient color (See {@link android.R.styleable#GradientColor}
* and {@link android.R.styleable#GradientColorItem}).
* If this property is animated, any value set by the animation will override the original value.
* No path fill is drawn if this property is not specified.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:strokeColor</code></dt>
* <dd>Specifies the color used to draw the path outline. May be a color or, for SDK 24+, a color
* state list or a gradient color (See {@link android.R.styleable#GradientColor}
* and {@link android.R.styleable#GradientColorItem}).
* If this property is animated, any value set by the animation will override the original value.
* No path outline is drawn if this property is not specified.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:strokeWidth</code></dt>
* <dd>The width a path stroke.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:strokeAlpha</code></dt>
* <dd>The opacity of a path stroke.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:fillAlpha</code></dt>
* <dd>The opacity to fill the path with.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:trimPathStart</code></dt>
* <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:trimPathEnd</code></dt>
* <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:trimPathOffset</code></dt>
* <dd>Shift trim region (allows showed region to include the start and end), in the range
* from 0 to 1.</dd>
+ * <dd>Animatable : Yes.</dd>
* <dt><code>android:strokeLineCap</code></dt>
* <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:strokeLineJoin</code></dt>
* <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:strokeMiterLimit</code></dt>
* <dd>Sets the Miter limit for a stroked path.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:fillType</code></dt>
* <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the
* same as SVG's "fill-rule" properties. For more details, see
* <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd>
+ * <dd>Animatable : No.</dd>
* </dl></dd>
+ *
* </dl>
*
* <dl>
@@ -183,9 +216,11 @@
* <dl>
* <dt><code>android:name</code></dt>
* <dd>Defines the name of the clip path.</dd>
+ * <dd>Animatable : No.</dd>
* <dt><code>android:pathData</code></dt>
* <dd>Defines clip path using the same format as "d" attribute
* in the SVG's path data.</dd>
+ * <dd>Animatable : Yes.</dd>
* </dl></dd>
* </dl>
* <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
@@ -710,7 +745,11 @@
groupStack.push(state.mRootGroup);
int eventType = parser.getEventType();
- while (eventType != XmlPullParser.END_DOCUMENT) {
+ final int innerDepth = parser.getDepth() + 1;
+
+ // Parse everything until the end of the vector element.
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
final VGroup currentGroup = groupStack.peek();
@@ -2112,41 +2151,61 @@
abstract Property getProperty(String propertyName);
}
- private static native long nCreateTree(long rootGroupPtr);
- private static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr);
- private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
- float viewportHeight);
- private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
- private static native float nGetRootAlpha(long rendererPtr);
- private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
-
private static native int nDraw(long rendererPtr, long canvasWrapperPtr,
long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
- private static native long nCreateFullPath();
- private static native long nCreateFullPath(long nativeFullPathPtr);
private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
int length);
+ private static native void nSetName(long nodePtr, String name);
+ private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
+ int length);
+ private static native void nSetPathString(long pathPtr, String pathString, int length);
+ // ------------- @FastNative ------------------
+
+ @FastNative
+ private static native long nCreateTree(long rootGroupPtr);
+ @FastNative
+ private static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr);
+ @FastNative
+ private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
+ float viewportHeight);
+ @FastNative
+ private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
+ @FastNative
+ private static native float nGetRootAlpha(long rendererPtr);
+ @FastNative
+ private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
+
+ @FastNative
+ private static native long nCreateFullPath();
+ @FastNative
+ private static native long nCreateFullPath(long nativeFullPathPtr);
+
+ @FastNative
private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
int strokeLineJoin, int fillType);
+ @FastNative
private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
+ @FastNative
private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
+ @FastNative
private static native long nCreateClipPath();
+ @FastNative
private static native long nCreateClipPath(long clipPathPtr);
+ @FastNative
private static native long nCreateGroup();
+ @FastNative
private static native long nCreateGroup(long groupPtr);
- private static native void nSetName(long nodePtr, String name);
- private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
- int length);
+ @FastNative
private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
float pivotY, float scaleX, float scaleY, float translateX, float translateY);
+ @FastNative
private static native void nAddChild(long groupPtr, long nodePtr);
- private static native void nSetPathString(long pathPtr, String pathString, int length);
/**
* The setters and getters below for paths and groups are here temporarily, and will be
@@ -2155,37 +2214,68 @@
* for VD during animation, and these setters and getters will be obsolete.
*/
// Setters and getters during animation.
+ @FastNative
private static native float nGetRotation(long groupPtr);
+ @FastNative
private static native void nSetRotation(long groupPtr, float rotation);
+ @FastNative
private static native float nGetPivotX(long groupPtr);
+ @FastNative
private static native void nSetPivotX(long groupPtr, float pivotX);
+ @FastNative
private static native float nGetPivotY(long groupPtr);
+ @FastNative
private static native void nSetPivotY(long groupPtr, float pivotY);
+ @FastNative
private static native float nGetScaleX(long groupPtr);
+ @FastNative
private static native void nSetScaleX(long groupPtr, float scaleX);
+ @FastNative
private static native float nGetScaleY(long groupPtr);
+ @FastNative
private static native void nSetScaleY(long groupPtr, float scaleY);
+ @FastNative
private static native float nGetTranslateX(long groupPtr);
+ @FastNative
private static native void nSetTranslateX(long groupPtr, float translateX);
+ @FastNative
private static native float nGetTranslateY(long groupPtr);
+ @FastNative
private static native void nSetTranslateY(long groupPtr, float translateY);
// Setters and getters for VPath during animation.
+ @FastNative
private static native void nSetPathData(long pathPtr, long pathDataPtr);
+ @FastNative
private static native float nGetStrokeWidth(long pathPtr);
+ @FastNative
private static native void nSetStrokeWidth(long pathPtr, float width);
+ @FastNative
private static native int nGetStrokeColor(long pathPtr);
+ @FastNative
private static native void nSetStrokeColor(long pathPtr, int strokeColor);
+ @FastNative
private static native float nGetStrokeAlpha(long pathPtr);
+ @FastNative
private static native void nSetStrokeAlpha(long pathPtr, float alpha);
+ @FastNative
private static native int nGetFillColor(long pathPtr);
+ @FastNative
private static native void nSetFillColor(long pathPtr, int fillColor);
+ @FastNative
private static native float nGetFillAlpha(long pathPtr);
+ @FastNative
private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
+ @FastNative
private static native float nGetTrimPathStart(long pathPtr);
+ @FastNative
private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
+ @FastNative
private static native float nGetTrimPathEnd(long pathPtr);
+ @FastNative
private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
+ @FastNative
private static native float nGetTrimPathOffset(long pathPtr);
+ @FastNative
private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
}
diff --git a/include/androidfw/Asset.h b/include/androidfw/Asset.h
index ee77e97..11709ce 100644
--- a/include/androidfw/Asset.h
+++ b/include/androidfw/Asset.h
@@ -26,11 +26,12 @@
#include <utils/Compat.h>
#include <utils/Errors.h>
-#include <utils/FileMap.h>
#include <utils/String8.h>
namespace android {
+class FileMap;
+
/*
* Instances of this class provide read-only operations on a byte stream.
*
@@ -44,7 +45,7 @@
*/
class Asset {
public:
- virtual ~Asset(void);
+ virtual ~Asset(void) = default;
static int32_t getGlobalCount();
static String8 getAssetAllocations();
@@ -119,6 +120,19 @@
const char* getAssetSource(void) const { return mAssetSource.string(); }
protected:
+ /*
+ * Adds this Asset to the global Asset list for debugging and
+ * accounting.
+ * Concrete subclasses must call this in their constructor.
+ */
+ static void registerAsset(Asset* asset);
+
+ /*
+ * Removes this Asset from the global Asset list.
+ * Concrete subclasses must call this in their destructor.
+ */
+ static void unregisterAsset(Asset* asset);
+
Asset(void); // constructor; only invoked indirectly
/* handle common seek() housekeeping */
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
index 0b22802..4377213 100644
--- a/include/androidfw/AssetManager.h
+++ b/include/androidfw/AssetManager.h
@@ -59,10 +59,7 @@
* single instance may be shared across multiple threads, and a single
* thread may have more than one instance (the latter is discouraged).
*
- * The purpose of the AssetManager is to create Asset objects. To do
- * this efficiently it may cache information about the locations of
- * files it has seen. This can be controlled with the "cacheMode"
- * argument.
+ * The purpose of the AssetManager is to create Asset objects.
*
* The asset hierarchy may be examined like a filesystem, using
* AssetDir objects to peruse a single directory.
@@ -73,24 +70,16 @@
static const char* IDMAP_BIN;
static const char* OVERLAY_DIR;
/*
- * If OVERLAY_SUBDIR_PROPERTY is set, search for runtime resource overlay
- * APKs in OVERLAY_SUBDIR/<value of OVERLAY_SUBDIR_PROPERTY>/ rather than in
+ * 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_SUBDIR;
- static const char* OVERLAY_SUBDIR_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;
- typedef enum CacheMode {
- CACHE_UNKNOWN = 0,
- CACHE_OFF, // don't try to cache file locations
- CACHE_DEFER, // construct cache as pieces are needed
- //CACHE_SCAN, // scan full(!) asset hierarchy at init() time
- } CacheMode;
-
- AssetManager(CacheMode cacheMode = CACHE_OFF);
+ AssetManager();
virtual ~AssetManager(void);
static int32_t getGlobalCount();
@@ -127,23 +116,16 @@
int32_t nextAssetPath(const int32_t cookie) const;
/*
- * Return an asset path in the manager. 'which' must be between 0 and
- * countAssetPaths().
+ * Return an asset path in the manager. 'cookie' must be a non-negative value
+ * previously returned from addAssetPath() or nextAssetPath().
*/
String8 getAssetPath(const int32_t cookie) const;
/*
- * Set the current locale and vendor. The locale can change during
- * the lifetime of an AssetManager if the user updates the device's
- * language setting. The vendor is less likely to change.
- *
- * Pass in NULL to indicate no preference.
- */
- void setLocale(const char* locale);
- void setVendor(const char* vendor);
-
- /*
- * Choose screen orientation for resources values returned.
+ * Sets various device configuration parameters, like screen orientation, layout,
+ * size, locale, etc.
+ * The optional 'locale' string takes precedence over the locale within 'config'
+ * and must be in bcp47 format.
*/
void setConfiguration(const ResTable_config& config, const char* locale = NULL);
@@ -154,9 +136,6 @@
/*
* Open an asset.
*
- * This will search through locale-specific and vendor-specific
- * directories and packages to find the file.
- *
* The object returned does not depend on the AssetManager. It should
* be freed by calling Asset::close().
*/
@@ -166,9 +145,8 @@
* Open a non-asset file as an asset.
*
* This is for opening files that are included in an asset package
- * but aren't assets. These sit outside the usual "locale/vendor"
- * path hierarchy, and will not be seen by "AssetDir" or included
- * in our filename cache.
+ * but aren't assets. These sit outside the usual "assets/"
+ * path hierarchy, and will not be seen by "AssetDir".
*/
Asset* openNonAsset(const char* fileName, AccessMode mode, int32_t* outCookie = NULL);
@@ -181,11 +159,6 @@
/*
* Open a directory within the asset hierarchy.
*
- * The contents of the directory are an amalgam of vendor-specific,
- * locale-specific, and generic assets stored loosely or in asset
- * packages. Depending on the cache setting and previous accesses,
- * this call may incur significant disk overhead.
- *
* To open the top-level directory, pass in "".
*/
AssetDir* openDir(const char* dirName);
@@ -193,11 +166,6 @@
/*
* Open a directory within a particular path of the asset manager.
*
- * The contents of the directory are an amalgam of vendor-specific,
- * locale-specific, and generic assets stored loosely or in asset
- * packages. Depending on the cache setting and previous accesses,
- * this call may incur significant disk overhead.
- *
* To open the top-level directory, pass in "".
*/
AssetDir* openNonAssetDir(const int32_t cookie, const char* dirName);
@@ -216,13 +184,6 @@
const ResTable& getResources(bool required = true) const;
/*
- * Discard cached filename information. This only needs to be called
- * if somebody has updated the set of "loose" files, and we want to
- * discard our cached notion of what's where.
- */
- void purge(void) { purgeFileNameCacheLocked(); }
-
- /*
* Return true if the files this AssetManager references are all
* up-to-date (have not been changed since it was created). If false
* is returned, you will need to create a new AssetManager to get
@@ -254,14 +215,8 @@
bool isSystemAsset;
};
- Asset* openInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& path);
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
const asset_path& path);
- Asset* openInLocaleVendorLocked(const char* fileName, AccessMode mode,
- const asset_path& path, const char* locale, const char* vendor);
- String8 createPathNameLocked(const asset_path& path, const char* locale,
- const char* vendor);
String8 createPathNameLocked(const asset_path& path, const char* rootDir);
String8 createZipSourceNameLocked(const String8& zipFileName,
const String8& dirName, const String8& fileName);
@@ -279,15 +234,6 @@
void mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
const SortedVector<AssetDir::FileInfo>* pContents);
- void loadFileNameCacheLocked(void);
- void fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
- const char* dirName);
- bool fncScanAndMergeDirLocked(
- SortedVector<AssetDir::FileInfo>* pMergedInfo,
- const asset_path& path, const char* locale, const char* vendor,
- const char* dirName);
- void purgeFileNameCacheLocked(void);
-
const ResTable* getResTable(bool required = true) const;
void setLocaleLocked(const char* locale);
void updateResourceParamsLocked() const;
@@ -344,8 +290,8 @@
*/
class ZipSet {
public:
- ZipSet(void);
- ~ZipSet(void);
+ ZipSet() = default;
+ ~ZipSet();
/*
* Return a ZipFileRO structure for a ZipFileRO with the specified
@@ -382,23 +328,9 @@
Vector<asset_path> mAssetPaths;
char* mLocale;
- char* mVendor;
mutable ResTable* mResources;
ResTable_config* mConfig;
-
- /*
- * Cached data for "loose" files. This lets us avoid poking at the
- * filesystem when searching for loose assets. Each entry is the
- * "extended partial" path, e.g. "default/default/foo/bar.txt". The
- * full set of files is present, including ".EXCLUDE" entries.
- *
- * We do not cache directory names. We don't retain the ".gz",
- * because to our clients "foo" and "foo.gz" both look like "foo".
- */
- CacheMode mCacheMode; // is the cache enabled?
- bool mCacheValid; // clear when locale or vendor changes
- SortedVector<AssetDir::FileInfo> mCache;
};
}; // namespace android
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
new file mode 100644
index 0000000..3ed8bce
--- /dev/null
+++ b/include/androidfw/AttributeResolution.h
@@ -0,0 +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 ANDROIDFW_ATTRIBUTERESOLUTION_H
+#define ANDROIDFW_ATTRIBUTERESOLUTION_H
+
+#include <androidfw/ResourceTypes.h>
+
+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
+// predict the rest of the iterations' branch correctly.
+// 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 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* 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* xml_parser, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
+
+} // namespace android
+
+#endif /* ANDROIDFW_ATTRIBUTERESOLUTION_H */
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 6349e86..08d6591 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -22,7 +22,6 @@
#include <androidfw/Asset.h>
#include <androidfw/LocaleData.h>
-#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/Vector.h>
diff --git a/include/androidfw/TypeWrappers.h b/include/androidfw/TypeWrappers.h
index 7bdf8af..4233b6b 100644
--- a/include/androidfw/TypeWrappers.h
+++ b/include/androidfw/TypeWrappers.h
@@ -18,6 +18,7 @@
#define __TYPE_WRAPPERS_H
#include <androidfw/ResourceTypes.h>
+#include <utils/ByteOrder.h>
namespace android {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 70e4b6f..00d786a 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -626,6 +626,16 @@
}
}
+ /**
+ * Notify keystore that the device went off-body.
+ */
+ public void onDeviceOffBody() {
+ try {
+ mBinder.onDeviceOffBody();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ }
+ }
/**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
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/Android.mk b/libs/androidfw/Android.mk
index 6bbfcd2..7d7d42c7 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -21,6 +21,7 @@
Asset.cpp \
AssetDir.cpp \
AssetManager.cpp \
+ AttributeResolution.cpp \
LocaleData.cpp \
misc.cpp \
ObbFile.cpp \
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 2cfa666..8e8c6a2 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -52,6 +52,47 @@
static Asset* gHead = NULL;
static Asset* gTail = NULL;
+void Asset::registerAsset(Asset* asset)
+{
+ AutoMutex _l(gAssetLock);
+ gCount++;
+ asset->mNext = asset->mPrev = NULL;
+ if (gTail == NULL) {
+ gHead = gTail = asset;
+ } else {
+ asset->mPrev = gTail;
+ gTail->mNext = asset;
+ gTail = asset;
+ }
+
+ if (kIsDebug) {
+ ALOGI("Creating Asset %p #%d\n", asset, gCount);
+ }
+}
+
+void Asset::unregisterAsset(Asset* asset)
+{
+ AutoMutex _l(gAssetLock);
+ gCount--;
+ if (gHead == asset) {
+ gHead = asset->mNext;
+ }
+ if (gTail == asset) {
+ gTail = asset->mPrev;
+ }
+ if (asset->mNext != NULL) {
+ asset->mNext->mPrev = asset->mPrev;
+ }
+ if (asset->mPrev != NULL) {
+ asset->mPrev->mNext = asset->mNext;
+ }
+ asset->mNext = asset->mPrev = NULL;
+
+ if (kIsDebug) {
+ ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
+ }
+}
+
int32_t Asset::getGlobalCount()
{
AutoMutex _l(gAssetLock);
@@ -79,43 +120,8 @@
}
Asset::Asset(void)
- : mAccessMode(ACCESS_UNKNOWN)
+ : mAccessMode(ACCESS_UNKNOWN), mNext(NULL), mPrev(NULL)
{
- AutoMutex _l(gAssetLock);
- gCount++;
- mNext = mPrev = NULL;
- if (gTail == NULL) {
- gHead = gTail = this;
- } else {
- mPrev = gTail;
- gTail->mNext = this;
- gTail = this;
- }
- if (kIsDebug) {
- ALOGI("Creating Asset %p #%d\n", this, gCount);
- }
-}
-
-Asset::~Asset(void)
-{
- AutoMutex _l(gAssetLock);
- gCount--;
- if (gHead == this) {
- gHead = mNext;
- }
- if (gTail == this) {
- gTail = mPrev;
- }
- if (mNext != NULL) {
- mNext->mPrev = mPrev;
- }
- if (mPrev != NULL) {
- mPrev->mNext = mNext;
- }
- mNext = mPrev = NULL;
- if (kIsDebug) {
- ALOGI("Destroying Asset in %p #%d\n", this, gCount);
- }
}
/*
@@ -361,6 +367,9 @@
_FileAsset::_FileAsset(void)
: mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
{
+ // Register the Asset with the global list here after it is fully constructed and its
+ // vtable pointer points to this concrete type. b/31113965
+ registerAsset(this);
}
/*
@@ -369,6 +378,10 @@
_FileAsset::~_FileAsset(void)
{
close();
+
+ // Unregister the Asset from the global list here before it is destructed and while its vtable
+ // pointer still points to this concrete type. b/31113965
+ unregisterAsset(this);
}
/*
@@ -685,6 +698,9 @@
: mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
{
+ // Register the Asset with the global list here after it is fully constructed and its
+ // vtable pointer points to this concrete type. b/31113965
+ registerAsset(this);
}
/*
@@ -693,6 +709,10 @@
_CompressedAsset::~_CompressedAsset(void)
{
close();
+
+ // Unregister the Asset from the global list here before it is destructed and while its vtable
+ // pointer still points to this concrete type. b/31113965
+ unregisterAsset(this);
}
/*
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index fe78cef..46fc9d4 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -35,6 +35,9 @@
#include <utils/threads.h>
#include <utils/Timers.h>
#include <utils/Trace.h>
+#ifndef _WIN32
+#include <sys/file.h>
+#endif
#include <assert.h>
#include <dirent.h>
@@ -56,12 +59,6 @@
static const bool kIsDebug = false;
-/*
- * Names for default app, locale, and vendor. We might want to change
- * these to be an actual locale, e.g. always use en-US as the default.
- */
-static const char* kDefaultLocale = "default";
-static const char* kDefaultVendor = "default";
static const char* kAssetsRoot = "assets";
static const char* kAppZipName = NULL; //"classes.jar";
static const char* kSystemAssets = "framework/framework-res.apk";
@@ -76,75 +73,70 @@
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_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::OVERLAY_SUBDIR = "/system/vendor/overlay-subdir";
-const char* AssetManager::OVERLAY_SUBDIR_PROPERTY = "ro.boot.vendor.overlay.subdir";
const char* AssetManager::IDMAP_DIR = "/data/resource-cache";
namespace {
- String8 idmapPathForPackagePath(const String8& pkgPath)
- {
- const char* root = getenv("ANDROID_DATA");
- LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
- String8 path(root);
- path.appendPath(kResourceCache);
- char buf[256]; // 256 chars should be enough for anyone...
- strncpy(buf, pkgPath.string(), 255);
- buf[255] = '\0';
- char* filename = buf;
- while (*filename && *filename == '/') {
- ++filename;
- }
- char* p = filename;
- while (*p) {
- if (*p == '/') {
- *p = '@';
- }
- ++p;
- }
- path.appendPath(filename);
- path.append("@idmap");
+String8 idmapPathForPackagePath(const String8& pkgPath) {
+ const char* root = getenv("ANDROID_DATA");
+ LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
+ String8 path(root);
+ path.appendPath(kResourceCache);
- return path;
+ char buf[256]; // 256 chars should be enough for anyone...
+ strncpy(buf, pkgPath.string(), 255);
+ buf[255] = '\0';
+ char* filename = buf;
+ while (*filename && *filename == '/') {
+ ++filename;
}
-
- /*
- * Like strdup(), but uses C++ "new" operator instead of malloc.
- */
- static char* strdupNew(const char* str)
- {
- char* newStr;
- int len;
-
- if (str == NULL)
- return NULL;
-
- len = strlen(str);
- newStr = new char[len+1];
- memcpy(newStr, str, len+1);
-
- return newStr;
+ char* p = filename;
+ while (*p) {
+ if (*p == '/') {
+ *p = '@';
+ }
+ ++p;
}
+ path.appendPath(filename);
+ path.append("@idmap");
+
+ return path;
}
/*
+ * Like strdup(), but uses C++ "new" operator instead of malloc.
+ */
+static char* strdupNew(const char* str) {
+ char* newStr;
+ int len;
+
+ if (str == NULL)
+ return NULL;
+
+ len = strlen(str);
+ newStr = new char[len+1];
+ memcpy(newStr, str, len+1);
+
+ return newStr;
+}
+
+} // namespace
+
+/*
* ===========================================================================
* AssetManager
* ===========================================================================
*/
-int32_t AssetManager::getGlobalCount()
-{
+int32_t AssetManager::getGlobalCount() {
return gCount;
}
-AssetManager::AssetManager(CacheMode cacheMode)
- : mLocale(NULL), mVendor(NULL),
- mResources(NULL), mConfig(new ResTable_config),
- mCacheMode(cacheMode), mCacheValid(false)
-{
+AssetManager::AssetManager() :
+ mLocale(NULL), mResources(NULL), mConfig(new ResTable_config) {
int count = android_atomic_inc(&gCount) + 1;
if (kIsDebug) {
ALOGI("Creating AssetManager %p #%d\n", this, count);
@@ -152,8 +144,7 @@
memset(mConfig, 0, sizeof(ResTable_config));
}
-AssetManager::~AssetManager(void)
-{
+AssetManager::~AssetManager() {
int count = android_atomic_dec(&gCount);
if (kIsDebug) {
ALOGI("Destroying AssetManager in %p #%d\n", this, count);
@@ -164,12 +155,10 @@
// don't have a String class yet, so make sure we clean up
delete[] mLocale;
- delete[] mVendor;
}
bool AssetManager::addAssetPath(
- const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset)
-{
+ const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset) {
AutoMutex _l(mLock);
asset_path ap;
@@ -344,25 +333,9 @@
return String8();
}
-/*
- * Set the current locale. Use NULL to indicate no locale.
- *
- * Close and reopen Zip archives as appropriate, and reset cached
- * information in the locale-specific sections of the tree.
- */
-void AssetManager::setLocale(const char* locale)
-{
- AutoMutex _l(mLock);
- setLocaleLocked(locale);
-}
-
-
void AssetManager::setLocaleLocked(const char* locale)
{
if (mLocale != NULL) {
- /* previously set, purge cached data */
- purgeFileNameCacheLocked();
- //mZipSet.purgeLocale();
delete[] mLocale;
}
@@ -370,25 +343,6 @@
updateResourceParamsLocked();
}
-/*
- * Set the current vendor. Use NULL to indicate no vendor.
- *
- * Close and reopen Zip archives as appropriate, and reset cached
- * information in the vendor-specific sections of the tree.
- */
-void AssetManager::setVendor(const char* vendor)
-{
- AutoMutex _l(mLock);
-
- if (mVendor != NULL) {
- /* previously set, purge cached data */
- purgeFileNameCacheLocked();
- //mZipSet.purgeVendor();
- delete[] mVendor;
- }
- mVendor = strdupNew(vendor);
-}
-
void AssetManager::setConfiguration(const ResTable_config& config, const char* locale)
{
AutoMutex _l(mLock);
@@ -413,23 +367,11 @@
/*
* Open an asset.
*
- * The data could be;
- * - In a file on disk (assetBase + fileName).
- * - In a compressed file on disk (assetBase + fileName.gz).
- * - In a Zip archive, uncompressed or compressed.
+ * The data could be in any asset path. Each asset path could be:
+ * - A directory on disk.
+ * - A Zip archive, uncompressed or compressed.
*
- * It can be in a number of different directories and Zip archives.
- * The search order is:
- * - [appname]
- * - locale + vendor
- * - "default" + vendor
- * - locale + "default"
- * - "default + "default"
- * - "common"
- * - (same as above)
- *
- * To find a particular file, we have to try up to eight paths with
- * all three forms of data.
+ * If the file is in a directory, it could have a .gz suffix, meaning it is compressed.
*
* We should probably reject requests for "illegal" filenames, e.g. those
* with illegal characters or "../" backward relative paths.
@@ -440,10 +382,6 @@
LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
-
- if (mCacheMode != CACHE_OFF && !mCacheValid)
- loadFileNameCacheLocked();
-
String8 assetName(kAssetsRoot);
assetName.appendPath(fileName);
@@ -468,8 +406,7 @@
/*
* Open a non-asset file as if it were an asset.
*
- * The "fileName" is the partial path starting from the application
- * name.
+ * The "fileName" is the partial path starting from the application name.
*/
Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t* outCookie)
{
@@ -477,10 +414,6 @@
LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
-
- if (mCacheMode != CACHE_OFF && !mCacheValid)
- loadFileNameCacheLocked();
-
/*
* For each top-level asset path, search for the asset.
*/
@@ -508,9 +441,6 @@
LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
- if (mCacheMode != CACHE_OFF && !mCacheValid)
- loadFileNameCacheLocked();
-
if (which < mAssetPaths.size()) {
ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
mAssetPaths.itemAt(which).path.string());
@@ -542,10 +472,11 @@
pAsset = open(fileName, Asset::ACCESS_STREAMING);
delete pAsset;
- if (pAsset == NULL)
+ if (pAsset == NULL) {
return kFileTypeNonexistent;
- else
+ } else {
return kFileTypeRegular;
+ }
}
bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
@@ -660,10 +591,6 @@
LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
}
- if (mCacheMode != CACHE_OFF && !mCacheValid) {
- const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
- }
-
mResources = new ResTable();
updateResourceParamsLocked();
@@ -723,6 +650,12 @@
return;
}
+#ifndef _WIN32
+ if (TEMP_FAILURE_RETRY(flock(fileno(fin), LOCK_SH)) != 0) {
+ fclose(fin);
+ return;
+ }
+#endif
char buf[1024];
while (fgets(buf, sizeof(buf), fin)) {
// format of each line:
@@ -753,6 +686,10 @@
const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap);
}
}
+
+#ifndef _WIN32
+ TEMP_FAILURE_RETRY(flock(fileno(fin), LOCK_UN));
+#endif
fclose(fin);
}
@@ -834,158 +771,6 @@
}
/*
- * Open an asset, searching for it in the directory hierarchy for the
- * specified app.
- *
- * Pass in a NULL values for "appName" if the common app directory should
- * be used.
- */
-Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& ap)
-{
- Asset* pAsset = NULL;
-
- /*
- * Try various combinations of locale and vendor.
- */
- if (mLocale != NULL && mVendor != NULL)
- pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor);
- if (pAsset == NULL && mVendor != NULL)
- pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor);
- if (pAsset == NULL && mLocale != NULL)
- pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL);
- if (pAsset == NULL)
- pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL);
-
- return pAsset;
-}
-
-/*
- * Open an asset, searching for it in the directory hierarchy for the
- * specified locale and vendor.
- *
- * We also search in "app.jar".
- *
- * Pass in NULL values for "appName", "locale", and "vendor" if the
- * defaults should be used.
- */
-Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode,
- const asset_path& ap, const char* locale, const char* vendor)
-{
- Asset* pAsset = NULL;
-
- if (ap.type == kFileTypeDirectory) {
- if (mCacheMode == CACHE_OFF) {
- /* look at the filesystem on disk */
- String8 path(createPathNameLocked(ap, locale, vendor));
- path.appendPath(fileName);
-
- String8 excludeName(path);
- excludeName.append(kExcludeExtension);
- if (::getFileType(excludeName.string()) != kFileTypeNonexistent) {
- /* say no more */
- //printf("+++ excluding '%s'\n", (const char*) excludeName);
- return kExcludedAsset;
- }
-
- pAsset = openAssetFromFileLocked(path, mode);
-
- if (pAsset == NULL) {
- /* try again, this time with ".gz" */
- path.append(".gz");
- pAsset = openAssetFromFileLocked(path, mode);
- }
-
- if (pAsset != NULL)
- pAsset->setAssetSource(path);
- } else {
- /* find in cache */
- String8 path(createPathNameLocked(ap, locale, vendor));
- path.appendPath(fileName);
-
- AssetDir::FileInfo tmpInfo;
- bool found = false;
-
- String8 excludeName(path);
- excludeName.append(kExcludeExtension);
-
- if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) {
- /* go no farther */
- //printf("+++ Excluding '%s'\n", (const char*) excludeName);
- return kExcludedAsset;
- }
-
- /*
- * File compression extensions (".gz") don't get stored in the
- * name cache, so we have to try both here.
- */
- if (mCache.indexOf(path) != NAME_NOT_FOUND) {
- found = true;
- pAsset = openAssetFromFileLocked(path, mode);
- if (pAsset == NULL) {
- /* try again, this time with ".gz" */
- path.append(".gz");
- pAsset = openAssetFromFileLocked(path, mode);
- }
- }
-
- if (pAsset != NULL)
- pAsset->setAssetSource(path);
-
- /*
- * Don't continue the search into the Zip files. Our cached info
- * said it was a file on disk; to be consistent with openDir()
- * we want to return the loose asset. If the cached file gets
- * removed, we fail.
- *
- * The alternative is to update our cache when files get deleted,
- * or make some sort of "best effort" promise, but for now I'm
- * taking the hard line.
- */
- if (found) {
- if (pAsset == NULL)
- ALOGD("Expected file not found: '%s'\n", path.string());
- return pAsset;
- }
- }
- }
-
- /*
- * Either it wasn't found on disk or on the cached view of the disk.
- * Dig through the currently-opened set of Zip files. If caching
- * is disabled, the Zip file may get reopened.
- */
- if (pAsset == NULL && ap.type == kFileTypeRegular) {
- String8 path;
-
- path.appendPath((locale != NULL) ? locale : kDefaultLocale);
- path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
- path.appendPath(fileName);
-
- /* check the appropriate Zip file */
- ZipFileRO* pZip = getZipFileLocked(ap);
- if (pZip != NULL) {
- //printf("GOT zip, checking '%s'\n", (const char*) path);
- ZipEntryRO entry = pZip->findEntryByName(path.string());
- if (entry != NULL) {
- //printf("FOUND in Zip file for %s/%s-%s\n",
- // appName, locale, vendor);
- pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
- pZip->releaseEntry(entry);
- }
- }
-
- if (pAsset != NULL) {
- /* create a "source" name, for debug/display */
- pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()),
- String8(""), String8(fileName)));
- }
- }
-
- return pAsset;
-}
-
-/*
* Create a "source name" for a file from a Zip archive.
*/
String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName,
@@ -1002,18 +787,6 @@
}
/*
- * Create a path to a loose asset (asset-base/app/locale/vendor).
- */
-String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale,
- const char* vendor)
-{
- String8 path(ap.path);
- path.appendPath((locale != NULL) ? locale : kDefaultLocale);
- path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
- return path;
-}
-
-/*
* Create a path to a loose asset (asset-base/app/rootDir).
*/
String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir)
@@ -1026,15 +799,6 @@
/*
* Return a pointer to one of our open Zip archives. Returns NULL if no
* matching Zip file exists.
- *
- * Right now we have 2 possible Zip files (1 each in app/"common").
- *
- * If caching is set to CACHE_OFF, to get the expected behavior we
- * need to reopen the Zip file on every request. That would be silly
- * and expensive, so instead we just check the file modification date.
- *
- * Pass in NULL values for "appName", "locale", and "vendor" if the
- * generics should be used.
*/
ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
{
@@ -1119,14 +883,10 @@
return pAsset;
}
-
-
/*
* Open a directory in the asset namespace.
*
- * An "asset directory" is simply the combination of all files in all
- * locations, with ".gz" stripped for loose files. With app, locale, and
- * vendor defined, we have 8 directories and 2 Zip archives to scan.
+ * An "asset directory" is simply the combination of all asset paths' "assets/" directories.
*
* Pass in "" for the root dir.
*/
@@ -1142,9 +902,6 @@
//printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
- if (mCacheMode != CACHE_OFF && !mCacheValid)
- loadFileNameCacheLocked();
-
pDir = new AssetDir;
/*
@@ -1187,9 +944,7 @@
/*
* Open a directory in the non-asset namespace.
*
- * An "asset directory" is simply the combination of all files in all
- * locations, with ".gz" stripped for loose files. With app, locale, and
- * vendor defined, we have 8 directories and 2 Zip archives to scan.
+ * An "asset directory" is simply the combination of all asset paths' "assets/" directories.
*
* Pass in "" for the root dir.
*/
@@ -1205,9 +960,6 @@
//printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
- if (mCacheMode != CACHE_OFF && !mCacheValid)
- loadFileNameCacheLocked();
-
pDir = new AssetDir;
pMergedInfo = new SortedVector<AssetDir::FileInfo>;
@@ -1248,74 +1000,17 @@
bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
const asset_path& ap, const char* rootDir, const char* dirName)
{
- SortedVector<AssetDir::FileInfo>* pContents;
- String8 path;
-
assert(pMergedInfo != NULL);
- //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
+ //printf("scanAndMergeDir: %s %s %s\n", ap.path.string(), rootDir, dirName);
- if (mCacheValid) {
- int i, start, count;
+ String8 path = createPathNameLocked(ap, rootDir);
+ if (dirName[0] != '\0')
+ path.appendPath(dirName);
- pContents = new SortedVector<AssetDir::FileInfo>;
-
- /*
- * Get the basic partial path and find it in the cache. That's
- * the start point for the search.
- */
- path = createPathNameLocked(ap, rootDir);
- if (dirName[0] != '\0')
- path.appendPath(dirName);
-
- start = mCache.indexOf(path);
- if (start == NAME_NOT_FOUND) {
- //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
- delete pContents;
- return false;
- }
-
- /*
- * The match string looks like "common/default/default/foo/bar/".
- * The '/' on the end ensures that we don't match on the directory
- * itself or on ".../foo/barfy/".
- */
- path.append("/");
-
- count = mCache.size();
-
- /*
- * Pick out the stuff in the current dir by examining the pathname.
- * It needs to match the partial pathname prefix, and not have a '/'
- * (fssep) anywhere after the prefix.
- */
- for (i = start+1; i < count; i++) {
- if (mCache[i].getFileName().length() > path.length() &&
- strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
- {
- const char* name = mCache[i].getFileName().string();
- // XXX THIS IS BROKEN! Looks like we need to store the full
- // path prefix separately from the file path.
- if (strchr(name + path.length(), '/') == NULL) {
- /* grab it, reducing path to just the filename component */
- AssetDir::FileInfo tmp = mCache[i];
- tmp.setFileName(tmp.getFileName().getPathLeaf());
- pContents->add(tmp);
- }
- } else {
- /* no longer in the dir or its subdirs */
- break;
- }
-
- }
- } else {
- path = createPathNameLocked(ap, rootDir);
- if (dirName[0] != '\0')
- path.appendPath(dirName);
- pContents = scanDirLocked(path);
- if (pContents == NULL)
- return false;
- }
+ SortedVector<AssetDir::FileInfo>* pContents = scanDirLocked(path);
+ if (pContents == NULL)
+ return false;
// if we wanted to do an incremental cache fill, we would do it here
@@ -1642,153 +1337,6 @@
#endif
}
-
-/*
- * Load all files into the file name cache. We want to do this across
- * all combinations of { appname, locale, vendor }, performing a recursive
- * directory traversal.
- *
- * This is not the most efficient data structure. Also, gathering the
- * information as we needed it (file-by-file or directory-by-directory)
- * would be faster. However, on the actual device, 99% of the files will
- * live in Zip archives, so this list will be very small. The trouble
- * is that we have to check the "loose" files first, so it's important
- * that we don't beat the filesystem silly looking for files that aren't
- * there.
- *
- * Note on thread safety: this is the only function that causes updates
- * to mCache, and anybody who tries to use it will call here if !mCacheValid,
- * so we need to employ a mutex here.
- */
-void AssetManager::loadFileNameCacheLocked(void)
-{
- assert(!mCacheValid);
- assert(mCache.size() == 0);
-
-#ifdef DO_TIMINGS // need to link against -lrt for this now
- DurationTimer timer;
- timer.start();
-#endif
-
- fncScanLocked(&mCache, "");
-
-#ifdef DO_TIMINGS
- timer.stop();
- ALOGD("Cache scan took %.3fms\n",
- timer.durationUsecs() / 1000.0);
-#endif
-
-#if 0
- int i;
- printf("CACHED FILE LIST (%d entries):\n", mCache.size());
- for (i = 0; i < (int) mCache.size(); i++) {
- printf(" %d: (%d) '%s'\n", i,
- mCache.itemAt(i).getFileType(),
- (const char*) mCache.itemAt(i).getFileName());
- }
-#endif
-
- mCacheValid = true;
-}
-
-/*
- * Scan up to 8 versions of the specified directory.
- */
-void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
- const char* dirName)
-{
- size_t i = mAssetPaths.size();
- while (i > 0) {
- i--;
- const asset_path& ap = mAssetPaths.itemAt(i);
- fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
- if (mLocale != NULL)
- fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
- if (mVendor != NULL)
- fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
- if (mLocale != NULL && mVendor != NULL)
- fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
- }
-}
-
-/*
- * Recursively scan this directory and all subdirs.
- *
- * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
- * files, and we prepend the extended partial path to the filenames.
- */
-bool AssetManager::fncScanAndMergeDirLocked(
- SortedVector<AssetDir::FileInfo>* pMergedInfo,
- const asset_path& ap, const char* locale, const char* vendor,
- const char* dirName)
-{
- SortedVector<AssetDir::FileInfo>* pContents;
- String8 partialPath;
- String8 fullPath;
-
- // XXX This is broken -- the filename cache needs to hold the base
- // asset path separately from its filename.
-
- partialPath = createPathNameLocked(ap, locale, vendor);
- if (dirName[0] != '\0') {
- partialPath.appendPath(dirName);
- }
-
- fullPath = partialPath;
- pContents = scanDirLocked(fullPath);
- if (pContents == NULL) {
- return false; // directory did not exist
- }
-
- /*
- * Scan all subdirectories of the current dir, merging what we find
- * into "pMergedInfo".
- */
- for (int i = 0; i < (int) pContents->size(); i++) {
- if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
- String8 subdir(dirName);
- subdir.appendPath(pContents->itemAt(i).getFileName());
-
- fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
- }
- }
-
- /*
- * To be consistent, we want entries for the root directory. If
- * we're the root, add one now.
- */
- if (dirName[0] == '\0') {
- AssetDir::FileInfo tmpInfo;
-
- tmpInfo.set(String8(""), kFileTypeDirectory);
- tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
- pContents->add(tmpInfo);
- }
-
- /*
- * We want to prepend the extended partial path to every entry in
- * "pContents". It's the same value for each entry, so this will
- * not change the sorting order of the vector contents.
- */
- for (int i = 0; i < (int) pContents->size(); i++) {
- const AssetDir::FileInfo& info = pContents->itemAt(i);
- pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
- }
-
- mergeInfoLocked(pMergedInfo, pContents);
- delete pContents;
- return true;
-}
-
-/*
- * Trash the cache.
- */
-void AssetManager::purgeFileNameCacheLocked(void)
-{
- mCacheValid = false;
- mCache.clear();
-}
-
/*
* ===========================================================================
* AssetManager::SharedZip
@@ -1838,6 +1386,7 @@
Asset* AssetManager::SharedZip::getResourceTableAsset()
{
+ AutoMutex _l(gLock);
ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
return mResourceTableAsset;
}
@@ -1847,10 +1396,10 @@
{
AutoMutex _l(gLock);
if (mResourceTableAsset == NULL) {
- mResourceTableAsset = asset;
// This is not thread safe the first time it is called, so
// do it here with the global lock held.
asset->getBuffer(true);
+ mResourceTableAsset = asset;
return asset;
}
}
@@ -1921,13 +1470,6 @@
*/
/*
- * Constructor.
- */
-AssetManager::ZipSet::ZipSet(void)
-{
-}
-
-/*
* Destructor. Close any open archives.
*/
AssetManager::ZipSet::~ZipSet(void)
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
new file mode 100644
index 0000000..00f7a42
--- /dev/null
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -0,0 +1,477 @@
+/*
+ * 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 "AttributeFinder.h"
+
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/ResourceTypes.h"
+
+#include <android/log.h>
+#include <cstdint>
+
+constexpr bool kDebugStyles = false;
+
+namespace android {
+
+class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> {
+ public:
+ explicit XmlAttributeFinder(const ResXMLParser* parser)
+ : BackTrackingAttributeFinder(0, parser != NULL ? parser->getAttributeCount() : 0),
+ parser_(parser) {}
+
+ inline uint32_t GetAttribute(size_t index) const { return parser_->getAttributeNameResID(index); }
+
+ 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) {}
+
+ inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const {
+ return entry->map.name.ident;
+ }
+};
+
+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("RETRIEVING ATTR 0x%08x...", cur_ident);
+ }
+
+ ssize_t block = -1;
+ 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.
+
+ // 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);
+ }
+ }
+
+ 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("-> 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 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);
+ }
+ 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);
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ 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 != -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;
+ }
+ }
+ }
+
+ // 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);
+ }
+
+ 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);
+ }
+ }
+
+ 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("-> From style: 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);
+ }
+ }
+ }
+
+ 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);
+ }
+ 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);
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ 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* xml_parser, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
+ ResTable_config config;
+ Res_value value;
+
+ int indices_idx = 0;
+
+ // Now lock down the resource object and start pulling stuff from it.
+ res->lock();
+
+ // 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;
+
+ // 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;
+
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ config.density = 0;
+
+ // 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);
+ }
+
+ 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;
+ }
+
+ // 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
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index b423f6c..b16279c 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -739,7 +739,7 @@
ALOGW("CREATING STRING CACHE OF %zu bytes",
static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**)));
#endif
- mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
+ mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*));
if (mCache == NULL) {
ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
(int)(mHeader->stringCount*sizeof(char16_t**)));
@@ -4390,10 +4390,12 @@
if (set->numAttrs >= set->availAttrs) {
// Need to alloc more memory...
const size_t newAvail = set->availAttrs+N;
+ void *oldSet = set;
set = (bag_set*)realloc(set,
sizeof(bag_set)
+ sizeof(bag_entry)*newAvail);
if (set == NULL) {
+ free(oldSet);
return NO_MEMORY;
}
set->availAttrs = newAvail;
@@ -4440,7 +4442,7 @@
pos++;
const size_t size = dtohs(map->value.size);
curOff += size + sizeof(*map)-sizeof(map->value);
- };
+ }
if (curEntry > set->numAttrs) {
set->numAttrs = curEntry;
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 2bc026b7..6837f25 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -22,6 +22,7 @@
testFiles := \
AppAsLib_test.cpp \
+ Asset_test.cpp \
AttributeFinder_test.cpp \
ByteBucketArray_test.cpp \
Config_test.cpp \
@@ -39,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
@@ -51,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 \
@@ -75,6 +77,7 @@
LOCAL_SHARED_LIBRARIES := \
libandroidfw \
+ libbase \
libcutils \
libutils \
libui \
diff --git a/libs/androidfw/tests/Asset_test.cpp b/libs/androidfw/tests/Asset_test.cpp
new file mode 100644
index 0000000..45c8cef
--- /dev/null
+++ b/libs/androidfw/tests/Asset_test.cpp
@@ -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.
+ */
+
+#include <androidfw/Asset.h>
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+TEST(AssetTest, FileAssetRegistersItself) {
+ const int32_t count = Asset::getGlobalCount();
+ Asset* asset = new _FileAsset();
+ EXPECT_EQ(count + 1, Asset::getGlobalCount());
+ delete asset;
+ EXPECT_EQ(count, Asset::getGlobalCount());
+}
+
+TEST(AssetTest, CompressedAssetRegistersItself) {
+ const int32_t count = Asset::getGlobalCount();
+ Asset* asset = new _CompressedAsset();
+ EXPECT_EQ(count + 1, Asset::getGlobalCount());
+ delete asset;
+ EXPECT_EQ(count, Asset::getGlobalCount());
+}
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 81a1831..4fe866f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,7 +2,7 @@
include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-BUGREPORT_FONT_CACHE_USAGE := true
+BUGREPORT_FONT_CACHE_USAGE := false
# Enables fine-grained GLES error checking
# If set to true, every GLES call is wrapped & error checked
@@ -10,6 +10,7 @@
HWUI_ENABLE_OPENGL_VALIDATION := false
hwui_src_files := \
+ hwui/Bitmap.cpp \
font/CacheTexture.cpp \
font/Font.cpp \
hwui/Canvas.cpp \
@@ -37,7 +38,6 @@
utils/Blur.cpp \
utils/GLUtils.cpp \
utils/LinearAllocator.cpp \
- utils/NinePatchImpl.cpp \
utils/StringUtils.cpp \
utils/TestWindowContext.cpp \
utils/VectorDrawableUtils.cpp \
@@ -45,7 +45,6 @@
AnimationContext.cpp \
Animator.cpp \
AnimatorManager.cpp \
- AssetAtlas.cpp \
BakedOpDispatcher.cpp \
BakedOpRenderer.cpp \
BakedOpState.cpp \
@@ -56,7 +55,6 @@
DeferredLayerUpdater.cpp \
DeviceInfo.cpp \
DisplayList.cpp \
- Dither.cpp \
Extensions.cpp \
FboCache.cpp \
FontRenderer.cpp \
@@ -81,6 +79,7 @@
PathParser.cpp \
PathTessellator.cpp \
PixelBuffer.cpp \
+ ProfileRenderer.cpp \
Program.cpp \
ProgramCache.cpp \
Properties.cpp \
@@ -95,6 +94,7 @@
ShadowTessellator.cpp \
SkiaCanvas.cpp \
SkiaCanvasProxy.cpp \
+ SkiaDisplayList.cpp \
SkiaShader.cpp \
Snapshot.cpp \
SpotShadow.cpp \
@@ -130,6 +130,14 @@
hwui_cflags += -DUSE_HWC2
endif
+# TODO: Linear blending should be enabled by default, but we are
+# TODO: making it an opt-in while it's a work in progress
+# TODO: The final test should be:
+# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false)
+ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true)
+ hwui_cflags += -DANDROID_ENABLE_LINEAR_BLENDING
+endif
+
# GCC false-positives on this warning, and since we -Werror that's
# a problem
hwui_cflags += -Wno-free-nonheap-object
@@ -143,7 +151,6 @@
hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE
endif
-
ifndef HWUI_COMPILE_SYMBOLS
hwui_cflags += -fvisibility=hidden
endif
@@ -258,6 +265,7 @@
tests/unit/BakedOpDispatcherTests.cpp \
tests/unit/BakedOpRendererTests.cpp \
tests/unit/BakedOpStateTests.cpp \
+ tests/unit/CanvasContextTests.cpp \
tests/unit/CanvasStateTests.cpp \
tests/unit/ClipAreaTests.cpp \
tests/unit/DamageAccumulatorTests.cpp \
@@ -280,6 +288,7 @@
tests/unit/RenderNodeTests.cpp \
tests/unit/RenderPropertiesTests.cpp \
tests/unit/SkiaBehaviorTests.cpp \
+ tests/unit/SkiaDisplayListTests.cpp \
tests/unit/SkiaCanvasTests.cpp \
tests/unit/SnapshotTests.cpp \
tests/unit/StringUtilsTests.cpp \
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
deleted file mode 100644
index e2e70372..0000000
--- a/libs/hwui/AssetAtlas.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 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 "AssetAtlas.h"
-#include "Caches.h"
-#include "Image.h"
-
-#include <GLES2/gl2ext.h>
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Lifecycle
-///////////////////////////////////////////////////////////////////////////////
-
-void AssetAtlas::init(const sp<GraphicBuffer>& buffer, int64_t* map, int count) {
- if (mImage) {
- return;
- }
-
- ATRACE_NAME("AssetAtlas::init");
-
- mImage = new Image(buffer);
- if (mImage->getTexture()) {
- if (!mTexture) {
- Caches& caches = Caches::getInstance();
- mTexture = new Texture(caches);
- mTexture->wrap(mImage->getTexture(),
- buffer->getWidth(), buffer->getHeight(), GL_RGBA);
- createEntries(caches, map, count);
- }
- } else {
- ALOGW("Could not create atlas image");
- terminate();
- }
-}
-
-void AssetAtlas::terminate() {
- delete mImage;
- mImage = nullptr;
- delete mTexture;
- mTexture = nullptr;
- mEntries.clear();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Entries
-///////////////////////////////////////////////////////////////////////////////
-
-AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const {
- auto result = mEntries.find(pixelRef);
- return result != mEntries.end() ? result->second.get() : nullptr;
-}
-
-Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const {
- auto result = mEntries.find(pixelRef);
- return result != mEntries.end() ? result->second->texture : nullptr;
-}
-
-/**
- * Delegates changes to wrapping and filtering to the base atlas texture
- * instead of applying the changes to the virtual textures.
- */
-struct DelegateTexture: public Texture {
- DelegateTexture(Caches& caches, Texture* delegate)
- : Texture(caches), mDelegate(delegate) { }
-
- virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false,
- bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
- mDelegate->setWrapST(wrapS, wrapT, bindTexture, force, renderTarget);
- }
-
- virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false,
- bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
- mDelegate->setFilterMinMag(min, mag, bindTexture, force, renderTarget);
- }
-
-private:
- Texture* const mDelegate;
-}; // struct DelegateTexture
-
-void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) {
- const float width = float(mTexture->width());
- const float height = float(mTexture->height());
-
- for (int i = 0; i < count; ) {
- SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]);
- // NOTE: We're converting from 64 bit signed values to 32 bit
- // signed values. This is guaranteed to be safe because the "x"
- // and "y" coordinate values are guaranteed to be representable
- // with 32 bits. The array is 64 bits wide so that it can carry
- // pointers on 64 bit architectures.
- const int x = static_cast<int>(map[i++]);
- const int y = static_cast<int>(map[i++]);
-
- // Bitmaps should never be null, we're just extra paranoid
- if (!pixelRef) continue;
-
- const UvMapper mapper(
- x / width, (x + pixelRef->info().width()) / width,
- y / height, (y + pixelRef->info().height()) / height);
-
- Texture* texture = new DelegateTexture(caches, mTexture);
- texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType());
- texture->wrap(mTexture->id(), pixelRef->info().width(),
- pixelRef->info().height(), mTexture->format());
-
- std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this));
- texture->uvMapper = &entry->uvMapper;
-
- mEntries.emplace(entry->pixelRef, std::move(entry));
- }
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
deleted file mode 100644
index b32e518..0000000
--- a/libs/hwui/AssetAtlas.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2013 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 ANDROID_HWUI_ASSET_ATLAS_H
-#define ANDROID_HWUI_ASSET_ATLAS_H
-
-#include "Texture.h"
-#include "UvMapper.h"
-
-#include <cutils/compiler.h>
-#include <GLES2/gl2.h>
-#include <ui/GraphicBuffer.h>
-#include <SkBitmap.h>
-
-#include <memory>
-#include <unordered_map>
-
-namespace android {
-namespace uirenderer {
-
-class Caches;
-class Image;
-
-/**
- * An asset atlas holds a collection of framework bitmaps in a single OpenGL
- * texture. Each bitmap is associated with a location, defined in pixels,
- * inside the atlas. The atlas is generated by the framework and bound as
- * an external texture using the EGLImageKHR extension.
- */
-class AssetAtlas {
-public:
- /**
- * Entry representing the texture and uvMapper of a PixelRef in the
- * atlas
- */
- class Entry {
- public:
- /*
- * A "virtual texture" object that represents the texture
- * this entry belongs to. This texture should never be
- * modified.
- */
- Texture* texture;
-
- /**
- * Maps texture coordinates in the [0..1] range into the
- * correct range to sample this entry from the atlas.
- */
- const UvMapper uvMapper;
-
- /**
- * Unique identifier used to merge bitmaps and 9-patches stored
- * in the atlas.
- */
- const void* getMergeId() const {
- return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey;
- }
-
- ~Entry() {
- delete texture;
- }
-
- private:
- /**
- * The pixel ref that generated this atlas entry.
- */
- SkPixelRef* pixelRef;
-
- /**
- * Atlas this entry belongs to.
- */
- const AssetAtlas& atlas;
-
- Entry(SkPixelRef* pixelRef, Texture* texture, const UvMapper& mapper,
- const AssetAtlas& atlas)
- : texture(texture)
- , uvMapper(mapper)
- , pixelRef(pixelRef)
- , atlas(atlas) {
- }
-
- friend class AssetAtlas;
- };
-
- AssetAtlas(): mTexture(nullptr), mImage(nullptr),
- mBlendKey(true), mOpaqueKey(false) { }
- ~AssetAtlas() { terminate(); }
-
- /**
- * Initializes the atlas with the specified buffer and
- * map. The buffer is a gralloc'd texture that will be
- * used as an EGLImage. The map is a list of SkBitmap*
- * and their (x, y) positions
- *
- * This method returns immediately if the atlas is already
- * initialized. To re-initialize the atlas, you must
- * first call terminate().
- */
- ANDROID_API void init(const sp<GraphicBuffer>& buffer, int64_t* map, int count);
-
- /**
- * Destroys the atlas texture. This object can be
- * re-initialized after calling this method.
- *
- * After calling this method, the width, height
- * and texture are set to 0.
- */
- void terminate();
-
- /**
- * Returns the width of this atlas in pixels.
- * Can return 0 if the atlas is not initialized.
- */
- uint32_t getWidth() const {
- return mTexture ? mTexture->width() : 0;
- }
-
- /**
- * Returns the height of this atlas in pixels.
- * Can return 0 if the atlas is not initialized.
- */
- uint32_t getHeight() const {
- return mTexture ? mTexture->height() : 0;
- }
-
- /**
- * Returns the OpenGL name of the texture backing this atlas.
- * Can return 0 if the atlas is not initialized.
- */
- GLuint getTexture() const {
- return mTexture ? mTexture->id() : 0;
- }
-
- /**
- * Returns the entry in the atlas associated with the specified
- * pixelRef. If the pixelRef is not in the atlas, return NULL.
- */
- Entry* getEntry(const SkPixelRef* pixelRef) const;
-
- /**
- * Returns the texture for the atlas entry associated with the
- * specified pixelRef. If the pixelRef is not in the atlas, return NULL.
- */
- Texture* getEntryTexture(const SkPixelRef* pixelRef) const;
-
-private:
- void createEntries(Caches& caches, int64_t* map, int count);
-
- Texture* mTexture;
- Image* mImage;
-
- const bool mBlendKey;
- const bool mOpaqueKey;
-
- std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries;
-}; // class AssetAtlas
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_ASSET_ATLAS_H
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 8b3f172..6079d5d 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -35,29 +35,24 @@
namespace android {
namespace uirenderer {
-static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) {
- vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top };
- vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top };
- vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom };
- vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom };
+static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds) {
+ vertices[0] = { bounds.left, bounds.top, 0, 0 };
+ vertices[1] = { bounds.right, bounds.top, 1, 0 };
+ vertices[2] = { bounds.left, bounds.bottom, 0, 1 };
+ vertices[3] = { bounds.right, bounds.bottom, 1, 1 };
}
void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
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;
- AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef());
- Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap);
+ Texture* texture = renderer.caches().textureCache.get(bitmap);
if (!texture) return;
const AutoTexture autoCleanup(texture);
TextureVertex vertices[opList.count * 4];
- Rect texCoords(0, 0, 1, 1);
- if (entry) {
- entry->uvMapper.map(texCoords);
- }
for (size_t i = 0; i < opList.count; i++) {
const BakedOpState& state = *(opList.states[i]);
TextureVertex* rectVerts = &vertices[i * 4];
@@ -69,7 +64,7 @@
// pure translate, so snap (same behavior as onBitmapOp)
opBounds.snapToPixelBoundaries();
}
- storeTexturedRect(rectVerts, opBounds, texCoords);
+ storeTexturedRect(rectVerts, opBounds);
renderer.dirtyRenderTarget(opBounds);
}
@@ -92,8 +87,6 @@
const MergedBakedOpList& opList) {
const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op));
const BakedOpState& firstState = *(opList.states[0]);
- AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(
- firstOp.bitmap->pixelRef());
// Batches will usually contain a small number of items so it's
// worth performing a first iteration to count the exact number
@@ -105,7 +98,7 @@
// TODO: cache mesh lookups
const Patch* opMesh = renderer.caches().patchCache.get(
- entry, op.bitmap->width(), op.bitmap->height(),
+ op.bitmap->width(), op.bitmap->height(),
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
totalVertices += opMesh->verticesCount;
}
@@ -126,7 +119,7 @@
// TODO: cache mesh lookups
const Patch* opMesh = renderer.caches().patchCache.get(
- entry, op.bitmap->width(), op.bitmap->height(),
+ op.bitmap->width(), op.bitmap->height(),
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
@@ -172,7 +165,7 @@
}
- Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap);
+ Texture* texture = renderer.caches().textureCache.get(firstOp.bitmap);
if (!texture) return;
const AutoTexture autoCleanup(texture);
@@ -299,7 +292,7 @@
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint);
TextDrawFunctor functor(&renderer, &state, renderClip,
x, y, pureTranslate, alpha, mode, op.paint);
@@ -442,7 +435,12 @@
}
void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) {
- const static UvMapper defaultUvMapper;
+ Texture* texture = renderer.caches().textureCache.get(op.bitmap);
+ if (!texture) {
+ return;
+ }
+ const AutoTexture autoCleanup(texture);
+
const uint32_t elementCount = op.meshWidth * op.meshHeight * 6;
std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]);
@@ -457,9 +455,6 @@
colors = tempColors.get();
}
- Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef());
- const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper);
-
for (int32_t y = 0; y < op.meshHeight; y++) {
for (int32_t x = 0; x < op.meshWidth; x++) {
uint32_t i = (y * (op.meshWidth + 1) + x) * 2;
@@ -469,8 +464,6 @@
float v1 = float(y) / op.meshHeight;
float v2 = float(y + 1) / op.meshHeight;
- mapper.map(u1, v1, u2, v2);
-
int ax = i + (op.meshWidth + 1) * 2;
int ay = ax + 1;
int bx = i;
@@ -491,14 +484,6 @@
}
}
- if (!texture) {
- texture = renderer.caches().textureCache.get(op.bitmap);
- if (!texture) {
- return;
- }
- }
- const AutoTexture autoCleanup(texture);
-
/*
* TODO: handle alpha_8 textures correctly by applying paint color, but *not*
* shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh.
@@ -543,7 +528,7 @@
void BakedOpDispatcher::onColorOp(BakedOpRenderer& renderer, const ColorOp& op, const BakedOpState& state) {
SkPaint paint;
paint.setColor(op.color);
- paint.setXfermodeMode(op.mode);
+ paint.setBlendMode(op.mode);
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -599,12 +584,11 @@
}
// TODO: avoid redoing the below work each frame:
- AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef());
const Patch* mesh = renderer.caches().patchCache.get(
- entry, op.bitmap->width(), op.bitmap->height(),
+ op.bitmap->width(), op.bitmap->height(),
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
- Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap);
+ Texture* texture = renderer.caches().textureCache.get(op.bitmap);
if (CC_LIKELY(texture)) {
const AutoTexture autoCleanup(texture);
Glop glop;
@@ -760,7 +744,7 @@
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint);
TextDrawFunctor functor(&renderer, &state, renderTargetClip,
0.0f, 0.0f, false, alpha, mode, op.paint);
@@ -792,11 +776,11 @@
}
void renderRectForLayer(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state,
- int color, SkXfermode::Mode mode, SkColorFilter* colorFilter) {
+ int color, SkBlendMode mode, SkColorFilter* colorFilter) {
SkPaint paint;
paint.setColor(color);
- paint.setXfermodeMode(mode);
- paint.setColorFilter(colorFilter);
+ paint.setBlendMode(mode);
+ paint.setColorFilter(sk_ref_sp(colorFilter));
RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint);
BakedOpDispatcher::onRectOp(renderer, rectOp, state);
}
@@ -824,11 +808,11 @@
if (CC_UNLIKELY(Properties::debugLayersUpdates)) {
// render debug layer highlight
renderRectForLayer(renderer, op, state,
- 0x7f00ff00, SkXfermode::Mode::kSrcOver_Mode, nullptr);
+ 0x7f00ff00, SkBlendMode::kSrcOver, nullptr);
} else if (CC_UNLIKELY(Properties::debugOverdraw)) {
// render transparent to increment overdraw for repaint area
renderRectForLayer(renderer, op, state,
- SK_ColorTRANSPARENT, SkXfermode::Mode::kSrcOver_Mode, nullptr);
+ SK_ColorTRANSPARENT, SkBlendMode::kSrcOver, nullptr);
}
}
}
@@ -845,14 +829,14 @@
if (op.paint && op.paint->getAlpha() < 255) {
SkPaint layerPaint;
layerPaint.setAlpha(op.paint->getAlpha());
- layerPaint.setXfermodeMode(SkXfermode::kDstIn_Mode);
- layerPaint.setColorFilter(op.paint->getColorFilter());
+ layerPaint.setBlendMode(SkBlendMode::kDstIn);
+ layerPaint.setColorFilter(sk_ref_sp(op.paint->getColorFilter()));
RectOp rectOp(state.computedState.clippedBounds, Matrix4::identity(), nullptr, &layerPaint);
BakedOpDispatcher::onRectOp(renderer, rectOp, state);
}
OffscreenBuffer& layer = **(op.layerHandle);
- auto mode = PaintUtils::getXfermodeDirect(op.paint);
+ auto mode = PaintUtils::getBlendModeDirect(op.paint);
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 6db345a..e8972aa 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -181,12 +181,8 @@
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
-Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
- Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
- if (!texture) {
- return mCaches.textureCache.get(bitmap);
- }
- return texture;
+Texture* BakedOpRenderer::getTexture(Bitmap* bitmap) {
+ return mCaches.textureCache.get(bitmap);
}
void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
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/Caches.cpp b/libs/hwui/Caches.cpp
index 741cdcc..b463e45 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -53,7 +53,6 @@
: gradientCache(mExtensions)
, patchCache(renderState)
, programCache(mExtensions)
- , dither(*this)
, mRenderState(&renderState)
, mInitialized(false) {
INIT_LOGD("Creating OpenGL renderer caches");
@@ -238,7 +237,6 @@
gradientCache.clear();
fontRenderer.clear();
fboCache.clear();
- dither.clear();
// fall through
case FlushMode::Moderate:
fontRenderer.flush();
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 344ee71..7c2e78c 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -16,8 +16,6 @@
#pragma once
-#include "AssetAtlas.h"
-#include "Dither.h"
#include "Extensions.h"
#include "FboCache.h"
#include "GammaFontRenderer.h"
@@ -130,6 +128,15 @@
TextureVertex* getRegionMesh();
/**
+ * Returns the GL RGBA internal format to use for the current device
+ * If the device supports linear blending and needSRGB is true,
+ * this function returns GL_SRGB8_ALPHA8, otherwise it returns GL_RGBA
+ */
+ constexpr GLint rgbaInternalFormat(bool needSRGB = true) const {
+ return extensions().hasSRGB() && needSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA;
+ }
+
+ /**
* Displays the memory usage of each cache and the total sum.
*/
void dumpMemoryUsage();
@@ -157,8 +164,6 @@
TaskManager tasks;
- Dither dither;
-
bool gpuPixelBuffersEnabled;
// Debug methods
@@ -169,7 +174,7 @@
void setProgram(const ProgramDescription& description);
void setProgram(Program* program);
- Extensions& extensions() { return mExtensions; }
+ const Extensions& extensions() const { return mExtensions; }
Program& program() { return *mProgram; }
PixelBufferState& pixelBufferState() { return *mPixelBufferState; }
TextureState& textureState() { return *mTextureState; }
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index c42ff1a..a7d5f60 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -45,7 +45,7 @@
void DeferredLayerUpdater::setPaint(const SkPaint* paint) {
mAlpha = PaintUtils::getAlphaDirect(paint);
- mMode = PaintUtils::getXfermodeDirect(paint);
+ mMode = PaintUtils::getBlendModeDirect(paint);
SkColorFilter* colorFilter = (paint) ? paint->getColorFilter() : nullptr;
SkRefCnt_SafeAssign(mColorFilter, colorFilter);
}
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 7420112..7335008 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -101,7 +101,7 @@
bool mBlend;
SkColorFilter* mColorFilter;
int mAlpha;
- SkXfermode::Mode mMode;
+ SkBlendMode mMode;
sp<GLConsumer> mSurfaceTexture;
SkMatrix* mTransform;
bool mNeedsGLContextAttach;
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index ca9e2bd..5213d48 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -19,10 +19,12 @@
#include <utils/Trace.h>
+#include "DamageAccumulator.h"
#include "Debug.h"
#include "DisplayList.h"
#include "RecordedOp.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
namespace android {
namespace uirenderer {
@@ -86,5 +88,46 @@
return index;
}
+void DisplayList::syncContents() {
+ for (auto& iter : functors) {
+ (*iter.functor)(DrawGlInfo::kModeSync, nullptr);
+ }
+ for (auto& vectorDrawable : vectorDrawables) {
+ vectorDrawable->syncProperties();
+ }
+}
+
+void DisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) {
+ for (auto&& child : children) {
+ updateFn(child->renderNode);
+ }
+}
+
+bool DisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+ TextureCache& cache = Caches::getInstance().textureCache;
+ for (auto& bitmapResource : bitmapResources) {
+ void* ownerToken = &info.canvasContext;
+ info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource.get());
+ }
+ for (auto&& op : children) {
+ RenderNode* childNode = op->renderNode;
+ info.damageAccumulator->pushTransform(&op->localMatrix);
+ bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
+ childFn(childNode, info, childFunctorsNeedLayer);
+ info.damageAccumulator->popTransform();
+ }
+
+ bool isDirty = false;
+ for (auto& vectorDrawable : vectorDrawables) {
+ // If any vector drawable in the display list needs update, damage the node.
+ if (vectorDrawable->isDirty()) {
+ isDirty = true;
+ }
+ vectorDrawable->setPropertyChangeWillBeConsumed(true);
+ }
+ return isDirty;
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index c5d8767..cab092f 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -17,6 +17,7 @@
#pragma once
#include <SkCamera.h>
+#include <SkDrawable.h>
#include <SkMatrix.h>
#include <private/hwui/DrawGlInfo.h>
@@ -36,6 +37,8 @@
#include "GlFunctorLifecycleListener.h"
#include "Matrix.h"
#include "RenderProperties.h"
+#include "TreeInfo.h"
+#include "hwui/Bitmap.h"
#include <vector>
@@ -89,7 +92,7 @@
};
DisplayList();
- ~DisplayList();
+ virtual ~DisplayList();
// index of DisplayListOp restore, after which projected descendants should be drawn
int projectionReceiveIndex;
@@ -99,9 +102,7 @@
const LsaVector<NodeOpType*>& getChildren() const { return children; }
- const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; }
- const LsaVector<FunctorContainer>& getFunctors() const { return functors; }
- const LsaVector<VectorDrawableRoot*>& getVectorDrawables() const { return vectorDrawables; }
+ const LsaVector<sk_sp<Bitmap>>& getBitmapResources() const { return bitmapResources; }
size_t addChild(NodeOpType* childOp);
@@ -113,15 +114,26 @@
size_t getUsedSize() {
return allocator.usedSize();
}
- bool isEmpty() {
- return ops.empty();
+
+ virtual bool isEmpty() const { return ops.empty(); }
+ virtual bool hasFunctor() const { return !functors.empty(); }
+ virtual bool hasVectorDrawables() const { return !vectorDrawables.empty(); }
+ virtual bool isSkiaDL() const { return false; }
+ virtual bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) {
+ return false;
}
-private:
+ virtual void syncContents();
+ virtual void updateChildren(std::function<void(RenderNode*)> updateFn);
+ virtual bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeInfo&, bool)> childFn);
+
+protected:
// allocator into which all ops and LsaVector arrays allocated
LinearAllocator allocator;
LinearStdAllocator<void*> stdAllocator;
+private:
LsaVector<Chunk> chunks;
LsaVector<BaseOpType*> ops;
@@ -129,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/Dither.cpp b/libs/hwui/Dither.cpp
deleted file mode 100644
index ec2013e..0000000
--- a/libs/hwui/Dither.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2012 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 "Caches.h"
-#include "Dither.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Lifecycle
-///////////////////////////////////////////////////////////////////////////////
-
-Dither::Dither(Caches& caches)
- : mCaches(caches)
- , mInitialized(false)
- , mDitherTexture(0) {
-}
-
-void Dither::bindDitherTexture() {
- if (!mInitialized) {
- glGenTextures(1, &mDitherTexture);
- mCaches.textureState().bindTexture(mDitherTexture);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-
- if (mCaches.extensions().hasFloatTextures()) {
- // We use a R16F texture, let's remap the alpha channel to the
- // red channel to avoid changing the shader sampling code on GL ES 3.0+
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
-
- float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE);
- const GLfloat pattern[] = {
- 0 * dither, 8 * dither, 2 * dither, 10 * dither,
- 12 * dither, 4 * dither, 14 * dither, 6 * dither,
- 3 * dither, 11 * dither, 1 * dither, 9 * dither,
- 15 * dither, 7 * dither, 13 * dither, 5 * dither
- };
-
- glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
- GL_RED, GL_FLOAT, &pattern);
- } else {
- const uint8_t pattern[] = {
- 0, 8, 2, 10,
- 12, 4, 14, 6,
- 3, 11, 1, 9,
- 15, 7, 13, 5
- };
-
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, &pattern);
- }
-
- mInitialized = true;
- } else {
- mCaches.textureState().bindTexture(mDitherTexture);
- }
-}
-
-void Dither::clear() {
- if (mInitialized) {
- mCaches.textureState().deleteTexture(mDitherTexture);
- mInitialized = false;
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Program management
-///////////////////////////////////////////////////////////////////////////////
-
-void Dither::setupProgram(Program& program, GLuint* textureUnit) {
- GLuint textureSlot = (*textureUnit)++;
- mCaches.textureState().activateTexture(textureSlot);
-
- bindDitherTexture();
-
- glUniform1i(program.getUniform("ditherSampler"), textureSlot);
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h
deleted file mode 100644
index 6af3e83..0000000
--- a/libs/hwui/Dither.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 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 ANDROID_HWUI_DITHER_H
-#define ANDROID_HWUI_DITHER_H
-
-#include <GLES3/gl3.h>
-
-namespace android {
-namespace uirenderer {
-
-class Caches;
-class Extensions;
-class Program;
-
-// Must be a power of two
-#define DITHER_KERNEL_SIZE 4
-// These must not use the .0f notation as they are used from GLSL
-#define DITHER_KERNEL_SIZE_INV (1.0 / 4.0)
-#define DITHER_KERNEL_SIZE_INV_SQUARE (1.0 / 16.0)
-
-/**
- * Handles dithering for programs.
- */
-class Dither {
-public:
- explicit Dither(Caches& caches);
-
- void clear();
- void setupProgram(Program& program, GLuint* textureUnit);
-
-private:
- void bindDitherTexture();
-
- Caches& mCaches;
- bool mInitialized;
- GLuint mDitherTexture;
-};
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_DITHER_H
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 02caaa4..1d67579 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -22,6 +22,7 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
+
#include <utils/Log.h>
namespace android {
@@ -45,6 +46,19 @@
mHas4BitStencil = extensions.has("GL_OES_stencil4");
mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ mHasSRGB = mVersionMajor >= 3 || extensions.has("GL_EXT_sRGB");
+ mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control");
+
+ // If linear blending is enabled, the device must have (ES3.0 or EXT_sRGB)
+ // and EXT_sRGB_write_control
+ LOG_ALWAYS_FATAL_IF(!mHasSRGB, "Linear blending requires ES 3.0 or EXT_sRGB");
+ LOG_ALWAYS_FATAL_IF(!mHasSRGBWriteControl, "Linear blending requires EXT_sRGB_write_control");
+#else
+ mHasSRGB = false;
+ mHasSRGBWriteControl = false;
+#endif
+
const char* version = (const char*) glGetString(GL_VERSION);
// Section 6.1.5 of the OpenGL ES specification indicates the GL version
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 67cc747..2c38507 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -43,6 +43,8 @@
inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; }
inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; }
inline bool hasFloatTextures() const { return mVersionMajor >= 3; }
+ inline bool hasSRGB() const { return mHasSRGB; }
+ inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; }
inline int getMajorGlVersion() const { return mVersionMajor; }
inline int getMinorGlVersion() const { return mVersionMinor; }
@@ -55,6 +57,8 @@
bool mHas1BitStencil;
bool mHas4BitStencil;
bool mHasUnpackSubImage;
+ bool mHasSRGB;
+ bool mHasSRGBWriteControl;
int mVersionMajor;
int mVersionMinor;
diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h
index 9a39ec2..9df7338 100644
--- a/libs/hwui/FloatColor.h
+++ b/libs/hwui/FloatColor.h
@@ -16,6 +16,7 @@
#ifndef FLOATCOLOR_H
#define FLOATCOLOR_H
+#include "utils/Color.h"
#include "utils/Macros.h"
#include "utils/MathUtils.h"
@@ -25,11 +26,25 @@
namespace uirenderer {
struct FloatColor {
+ // "color" is a gamma-encoded sRGB color
+ // After calling this method, the color is stored as a pre-multiplied linear color
+ // if linear blending is enabled. Otherwise, the color is stored as a pre-multiplied
+ // gamma-encoded sRGB color
void set(uint32_t color) {
a = ((color >> 24) & 0xff) / 255.0f;
- r = a * ((color >> 16) & 0xff) / 255.0f;
- g = a * ((color >> 8) & 0xff) / 255.0f;
- b = a * ((color ) & 0xff) / 255.0f;
+ r = a * EOCF(((color >> 16) & 0xff) / 255.0f);
+ g = a * EOCF(((color >> 8) & 0xff) / 255.0f);
+ b = a * EOCF(((color ) & 0xff) / 255.0f);
+ }
+
+ // "color" is a gamma-encoded sRGB color
+ // After calling this method, the color is stored as a pre-multiplied linear color
+ // if linear blending is enabled.
+ void setSRGB(uint32_t color) {
+ a = ((color >> 24) & 0xff) / 255.0f;
+ r = a * EOCF_sRGB(((color >> 16) & 0xff) / 255.0f);
+ g = a * EOCF_sRGB(((color >> 8) & 0xff) / 255.0f);
+ b = a * EOCF_sRGB(((color ) & 0xff) / 255.0f);
}
bool isNotBlack() {
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 9b60dfc..effc65e 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -60,11 +60,17 @@
}
int transformFlags = pureTranslate
? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ bool gammaCorrection = true;
+#else
+ bool gammaCorrection = false;
+#endif
Glop glop;
GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
.setRoundRectClipState(bakedState->roundRectClipState)
.setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
.setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+ .setGammaCorrection(gammaCorrection)
.setTransform(bakedState->computedState.transform, transformFlags)
.setModelViewIdentityEmptyBounds()
.build();
@@ -287,24 +293,23 @@
// Copy the glyph image, taking the mask format into account
switch (format) {
case SkMask::kA8_Format: {
- uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
- TEXTURE_BORDER_SIZE;
// write leading border line
memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
// write glyph data
if (mGammaTable) {
- for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
+ for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
row = cacheY * cacheWidth;
cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
- for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
+ for (uint32_t cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
uint8_t tempCol = bitmapBuffer[bY + bX];
cacheBuffer[row + cacheX] = mGammaTable[tempCol];
}
cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
}
} else {
- for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
+ for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
row = cacheY * cacheWidth;
memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index e836c20..dd9c40f 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -58,7 +58,7 @@
const BakedOpState* bakedState,
const ClipBase* clip,
float x, float y, bool pureTranslate,
- int alpha, SkXfermode::Mode mode, const SkPaint* paint)
+ int alpha, SkBlendMode mode, const SkPaint* paint)
: renderer(renderer)
, bakedState(bakedState)
, clip(clip)
@@ -79,7 +79,7 @@
float y;
bool pureTranslate;
int alpha;
- SkXfermode::Mode mode;
+ SkBlendMode mode;
const SkPaint* paint;
};
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index be4fdac..245db1d 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -591,7 +591,7 @@
}
static bool hasMergeableClip(const BakedOpState& state) {
- return state.computedState.clipState
+ return !state.computedState.clipState
|| state.computedState.clipState->mode == ClipMode::Rectangle;
}
@@ -608,11 +608,10 @@
// MergingDrawBatch::canMergeWith()
if (bakedState->computedState.transform.isSimple()
&& bakedState->computedState.transform.positiveScale()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
&& op.bitmap->colorType() != kAlpha_8_SkColorType
&& hasMergeableClip(*bakedState)) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
- // TODO: AssetAtlas in mergeId
currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
} else {
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
@@ -632,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,
@@ -684,10 +683,9 @@
if (!bakedState) return; // quick rejected
if (bakedState->computedState.transform.isPureTranslate()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
&& hasMergeableClip(*bakedState)) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
- // TODO: AssetAtlas in mergeId
// Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
@@ -752,7 +750,7 @@
batchid_t batchId = textBatchId(*(op.paint));
if (bakedState->computedState.transform.isPureTranslate()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
&& hasMergeableClip(*bakedState)) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 570322d..d3adc32 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -16,6 +16,7 @@
#include "FrameInfoVisualizer.h"
#include "BakedOpRenderer.h"
+#include "IProfileRenderer.h"
#include "utils/Color.h"
#include <cutils/compiler.h>
@@ -88,7 +89,7 @@
}
}
-void FrameInfoVisualizer::draw(ContentRenderer* renderer) {
+void FrameInfoVisualizer::draw(IProfileRenderer& renderer) {
RETURN_IF_DISABLED();
if (mShowDirtyRegions) {
@@ -96,8 +97,8 @@
if (mFlashToggle) {
SkPaint paint;
paint.setColor(0x7fff0000);
- renderer->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop,
- mDirtyRegion.fRight, mDirtyRegion.fBottom, &paint);
+ renderer.drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop,
+ mDirtyRegion.fRight, mDirtyRegion.fBottom, paint);
}
}
@@ -111,7 +112,7 @@
info.markSwapBuffers();
info.markFrameCompleted();
- initializeRects(renderer->getViewportHeight(), renderer->getViewportWidth());
+ initializeRects(renderer.getViewportHeight(), renderer.getViewportWidth());
drawGraph(renderer);
drawThreshold(renderer);
}
@@ -194,26 +195,26 @@
}
}
-void FrameInfoVisualizer::drawGraph(ContentRenderer* renderer) {
+void FrameInfoVisualizer::drawGraph(IProfileRenderer& renderer) {
SkPaint paint;
for (size_t i = 0; i < Bar.size(); i++) {
nextBarSegment(Bar[i].start, Bar[i].end);
paint.setColor(Bar[i].color & BAR_FAST_MASK);
- renderer->drawRects(mFastRects.get(), mNumFastRects * 4, &paint);
+ renderer.drawRects(mFastRects.get(), mNumFastRects * 4, paint);
paint.setColor(Bar[i].color & BAR_JANKY_MASK);
- renderer->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint);
+ renderer.drawRects(mJankyRects.get(), mNumJankyRects * 4, paint);
}
}
-void FrameInfoVisualizer::drawThreshold(ContentRenderer* renderer) {
+void FrameInfoVisualizer::drawThreshold(IProfileRenderer& renderer) {
SkPaint paint;
paint.setColor(THRESHOLD_COLOR);
- float yLocation = renderer->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
- renderer->drawRect(0.0f,
+ float yLocation = renderer.getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
+ renderer.drawRect(0.0f,
yLocation - mThresholdStroke/2,
- renderer->getViewportWidth(),
+ renderer.getViewportWidth(),
yLocation + mThresholdStroke/2,
- &paint);
+ paint);
}
bool FrameInfoVisualizer::consumeProperties() {
diff --git a/libs/hwui/FrameInfoVisualizer.h b/libs/hwui/FrameInfoVisualizer.h
index d60c002..b98f501 100644
--- a/libs/hwui/FrameInfoVisualizer.h
+++ b/libs/hwui/FrameInfoVisualizer.h
@@ -28,8 +28,7 @@
namespace android {
namespace uirenderer {
-class BakedOpRenderer;
-typedef BakedOpRenderer ContentRenderer;
+class IProfileRenderer;
// TODO: This is a bit awkward as it needs to match the thing in CanvasContext
// A better abstraction here would be nice but iterators are painful
@@ -47,7 +46,7 @@
void setDensity(float density);
void unionDirty(SkRect* dirty);
- void draw(ContentRenderer* renderer);
+ void draw(IProfileRenderer& renderer);
void dumpData(int fd);
@@ -57,8 +56,8 @@
void initializeRects(const int baseline, const int width);
void nextBarSegment(FrameInfoIndex start, FrameInfoIndex end);
- void drawGraph(ContentRenderer* renderer);
- void drawThreshold(ContentRenderer* renderer);
+ void drawGraph(IProfileRenderer& renderer);
+ void drawThreshold(IProfileRenderer& renderer);
inline float durationMS(size_t index, FrameInfoIndex start, FrameInfoIndex end) {
float duration = mFrameSource[index].duration(start, end) * 0.000001f;
diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp
index 96cac86..8aff0a2 100644
--- a/libs/hwui/GammaFontRenderer.cpp
+++ b/libs/hwui/GammaFontRenderer.cpp
@@ -24,12 +24,13 @@
GammaFontRenderer::GammaFontRenderer() {
INIT_LOGD("Creating lookup gamma font renderer");
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
// Compute the gamma tables
const float gamma = 1.0f / Properties::textGamma;
-
for (uint32_t i = 0; i <= 255; i++) {
mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f));
}
+#endif
}
void GammaFontRenderer::endPrecaching() {
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index bd27a1a..c9cf69b 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -18,11 +18,6 @@
#define ANDROID_HWUI_GAMMA_FONT_RENDERER_H
#include "FontRenderer.h"
-#include "Program.h"
-
-#include <SkPaint.h>
-
-#include <utils/String8.h>
namespace android {
namespace uirenderer {
@@ -43,7 +38,11 @@
FontRenderer& getFontRenderer() {
if (!mRenderer) {
- mRenderer.reset(new FontRenderer(&mGammaTable[0]));
+ const uint8_t* table = nullptr;
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
+ table = &mGammaTable[0];
+#endif
+ mRenderer.reset(new FontRenderer(table));
}
return *mRenderer;
}
@@ -64,7 +63,9 @@
private:
std::unique_ptr<FontRenderer> mRenderer;
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
uint8_t mGammaTable[256];
+#endif
};
}; // namespace uirenderer
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 1091736..f14b50a 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -220,19 +220,19 @@
////////////////////////////////////////////////////////////////////////////////
void GlopBuilder::setFill(int color, float alphaScale,
- SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
+ SkBlendMode mode, Blend::ModeOrderSwap modeUsage,
const SkShader* shader, const SkColorFilter* colorFilter) {
- if (mode != SkXfermode::kClear_Mode) {
- float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
+ if (mode != SkBlendMode::kClear) {
if (!shader) {
- float colorScale = alpha / 255.0f;
- mOutGlop->fill.color = {
- colorScale * SkColorGetR(color),
- colorScale * SkColorGetG(color),
- colorScale * SkColorGetB(color),
- alpha
- };
+ FloatColor c;
+ c.set(color);
+ c.r *= alphaScale;
+ c.g *= alphaScale;
+ c.b *= alphaScale;
+ c.a *= alphaScale;
+ mOutGlop->fill.color = c;
} else {
+ float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
mOutGlop->fill.color = { 1, 1, 1, alpha };
}
} else {
@@ -246,8 +246,8 @@
|| mOutGlop->roundRectClipState
|| PaintUtils::isBlendedShader(shader)
|| PaintUtils::isBlendedColorFilter(colorFilter)
- || mode != SkXfermode::kSrcOver_Mode) {
- if (CC_LIKELY(mode <= SkXfermode::kScreen_Mode)) {
+ || mode != SkBlendMode::kSrcOver) {
+ if (CC_LIKELY(mode <= SkBlendMode::kScreen)) {
Blend::getFactors(mode, modeUsage,
&mOutGlop->blend.src, &mOutGlop->blend.dst);
} else {
@@ -257,12 +257,12 @@
// If the blend mode cannot be implemented using shaders, fall
// back to the default SrcOver blend mode instead
if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) {
- mDescription.framebufferMode = mode;
+ mDescription.framebufferMode = (SkXfermode::Mode)mode;
mDescription.swapSrcDst = (modeUsage == Blend::ModeOrderSwap::Swap);
// blending in shader, don't enable
} else {
// unsupported
- Blend::getFactors(SkXfermode::kSrcOver_Mode, modeUsage,
+ Blend::getFactors(SkBlendMode::kSrcOver, modeUsage,
&mOutGlop->blend.src, &mOutGlop->blend.dst);
}
}
@@ -271,20 +271,12 @@
if (colorFilter) {
SkColor color;
- SkXfermode::Mode mode;
+ SkXfermode::Mode xmode;
SkScalar srcColorMatrix[20];
- if (colorFilter->asColorMode(&color, &mode)) {
+ if (colorFilter->asColorMode(&color, &xmode)) {
mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend;
- mDescription.colorMode = mode;
-
- const float alpha = SkColorGetA(color) / 255.0f;
- float colorScale = alpha / 255.0f;
- mOutGlop->fill.filter.color = {
- colorScale * SkColorGetR(color),
- colorScale * SkColorGetG(color),
- colorScale * SkColorGetB(color),
- alpha,
- };
+ mDescription.colorMode = xmode;
+ mOutGlop->fill.filter.color.set(color);
} else if (colorFilter->asColorMatrix(srcColorMatrix)) {
mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix;
@@ -297,10 +289,10 @@
// Skia uses the range [0..255] for the addition vector, but we need
// the [0..1] range to apply the vector in GLSL
float* colorVector = mOutGlop->fill.filter.matrix.vector;
- colorVector[0] = srcColorMatrix[4] / 255.0f;
- colorVector[1] = srcColorMatrix[9] / 255.0f;
- colorVector[2] = srcColorMatrix[14] / 255.0f;
- colorVector[3] = srcColorMatrix[19] / 255.0f;
+ colorVector[0] = EOCF(srcColorMatrix[4] / 255.0f);
+ colorVector[1] = EOCF(srcColorMatrix[9] / 255.0f);
+ colorVector[2] = EOCF(srcColorMatrix[14] / 255.0f);
+ colorVector[3] = srcColorMatrix[19] / 255.0f; // alpha is linear
} else {
LOG_ALWAYS_FATAL("unsupported ColorFilter");
}
@@ -329,7 +321,7 @@
shader = nullptr;
}
setFill(color, alphaScale,
- PaintUtils::getXfermode(paint->getXfermode()), Blend::ModeOrderSwap::NoSwap,
+ paint->getBlendMode(), Blend::ModeOrderSwap::NoSwap,
shader, paint->getColorFilter());
} else {
mOutGlop->fill.color = { alphaScale, alphaScale, alphaScale, alphaScale };
@@ -338,7 +330,7 @@
|| (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha)
|| texture.blend
|| mOutGlop->roundRectClipState) {
- Blend::getFactors(SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
+ Blend::getFactors(SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap,
&mOutGlop->blend.src, &mOutGlop->blend.dst);
} else {
mOutGlop->blend = { GL_ZERO, GL_ZERO };
@@ -368,7 +360,7 @@
}
setFill(paint.getColor(), alphaScale,
- PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
+ paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.useShadowAlphaInterp = shadowInterp;
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
@@ -384,7 +376,7 @@
mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
setFill(paint.getColor(), alphaScale,
- PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
+ paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.hasAlpha8Texture = true;
@@ -408,7 +400,7 @@
}
setFill(shadowColor, alphaScale,
- PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
+ paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.hasAlpha8Texture = true;
@@ -421,7 +413,7 @@
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
- setFill(SK_ColorBLACK, 1.0f, SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
+ setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap,
nullptr, nullptr);
return *this;
}
@@ -431,13 +423,13 @@
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
- setFill(SK_ColorBLACK, 1.0f, SkXfermode::kClear_Mode, Blend::ModeOrderSwap::NoSwap,
+ setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kClear, Blend::ModeOrderSwap::NoSwap,
nullptr, nullptr);
return *this;
}
GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
- float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage) {
+ float alpha, SkBlendMode mode, Blend::ModeOrderSwap modeUsage) {
TRIGGER_STAGE(kFillStage);
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
@@ -473,7 +465,7 @@
GL_TEXTURE_EXTERNAL_OES, GL_LINEAR, GL_CLAMP_TO_EDGE,
&textureTransform };
- setFill(SK_ColorWHITE, 1.0f, SkXfermode::kSrc_Mode, Blend::ModeOrderSwap::NoSwap,
+ setFill(SK_ColorWHITE, 1.0f, SkBlendMode::kSrc, Blend::ModeOrderSwap::NoSwap,
nullptr, nullptr);
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
@@ -481,6 +473,13 @@
return *this;
}
+GlopBuilder& GlopBuilder::setGammaCorrection(bool enabled) {
+ REQUIRE_STAGES(kFillStage);
+
+ mDescription.hasGammaCorrection = enabled;
+ return *this;
+}
+
////////////////////////////////////////////////////////////////////////////////
// Transform
////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 1152461..d511ccb 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -70,7 +70,7 @@
GlopBuilder& setFillBlack();
GlopBuilder& setFillClear();
GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
- float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage);
+ float alpha, SkBlendMode mode, Blend::ModeOrderSwap modeUsage);
GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
// TODO: Texture should probably know and own its target.
// setFillLayer() forces it to GL_TEXTURE which isn't always correct.
@@ -105,12 +105,14 @@
GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
+ GlopBuilder& setGammaCorrection(bool enabled);
+
void build();
static void dump(const Glop& glop);
private:
void setFill(int color, float alphaScale,
- SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
+ SkBlendMode mode, Blend::ModeOrderSwap modeUsage,
const SkShader* shader, const SkColorFilter* colorFilter);
enum StageFlags {
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index c8f5e94..0972ac1 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -67,7 +67,8 @@
, mSize(0)
, mMaxSize(Properties::gradientCacheSize)
, mUseFloatTexture(extensions.hasFloatTextures())
- , mHasNpot(extensions.hasNPot()){
+ , mHasNpot(extensions.hasNPot())
+ , mHasSRGB(extensions.hasSRGB()) {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
mCache.setOnEntryRemovedListener(this);
@@ -176,71 +177,56 @@
size_t GradientCache::bytesPerPixel() const {
// We use 4 channels (RGBA)
+ return 4 * (mUseFloatTexture ? /* fp16 */ 2 : sizeof(uint8_t));
+}
+
+size_t GradientCache::sourceBytesPerPixel() const {
+ // We use 4 channels (RGBA) and upload from floats (not half floats)
return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t));
}
-void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const {
- outColor.r = (inColor >> 16) & 0xff;
- outColor.g = (inColor >> 8) & 0xff;
- outColor.b = (inColor >> 0) & 0xff;
- outColor.a = (inColor >> 24) & 0xff;
-}
-
-void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const {
- outColor.r = ((inColor >> 16) & 0xff) / 255.0f;
- outColor.g = ((inColor >> 8) & 0xff) / 255.0f;
- outColor.b = ((inColor >> 0) & 0xff) / 255.0f;
- outColor.a = ((inColor >> 24) & 0xff) / 255.0f;
-}
-
-void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount,
- uint8_t*& dst) const {
+void GradientCache::mixBytes(const FloatColor& start, const FloatColor& end,
+ float amount, uint8_t*& dst) const {
float oppAmount = 1.0f - amount;
- const float alpha = start.a * oppAmount + end.a * amount;
- const float a = alpha / 255.0f;
-
- *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount));
- *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount));
- *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount));
- *dst++ = uint8_t(alpha);
+ *dst++ = uint8_t(OECF_sRGB(start.r * oppAmount + end.r * amount) * 255.0f);
+ *dst++ = uint8_t(OECF_sRGB(start.g * oppAmount + end.g * amount) * 255.0f);
+ *dst++ = uint8_t(OECF_sRGB(start.b * oppAmount + end.b * amount) * 255.0f);
+ *dst++ = uint8_t( (start.a * oppAmount + end.a * amount) * 255.0f);
}
-void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount,
- uint8_t*& dst) const {
+void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end,
+ float amount, uint8_t*& dst) const {
float oppAmount = 1.0f - amount;
- const float a = start.a * oppAmount + end.a * amount;
-
float* d = (float*) dst;
- *d++ = a * (start.r * oppAmount + end.r * amount);
- *d++ = a * (start.g * oppAmount + end.g * amount);
- *d++ = a * (start.b * oppAmount + end.b * amount);
- *d++ = a;
-
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ *d++ = start.r * oppAmount + end.r * amount;
+ *d++ = start.g * oppAmount + end.g * amount;
+ *d++ = start.b * oppAmount + end.b * amount;
+#else
+ *d++ = OECF_sRGB(start.r * oppAmount + end.r * amount);
+ *d++ = OECF_sRGB(start.g * oppAmount + end.g * amount);
+ *d++ = OECF_sRGB(start.b * oppAmount + end.b * amount);
+#endif
+ *d++ = start.a * oppAmount + end.a * amount;
dst += 4 * sizeof(float);
}
void GradientCache::generateTexture(uint32_t* colors, float* positions,
const uint32_t width, const uint32_t height, Texture* texture) {
- const GLsizei rowBytes = width * bytesPerPixel();
+ const GLsizei rowBytes = width * sourceBytesPerPixel();
uint8_t pixels[rowBytes * height];
- static ChannelSplitter gSplitters[] = {
- &android::uirenderer::GradientCache::splitToBytes,
- &android::uirenderer::GradientCache::splitToFloats,
- };
- ChannelSplitter split = gSplitters[mUseFloatTexture];
-
static ChannelMixer gMixers[] = {
- &android::uirenderer::GradientCache::mixBytes,
- &android::uirenderer::GradientCache::mixFloats,
+ &android::uirenderer::GradientCache::mixBytes, // colors are stored gamma-encoded
+ &android::uirenderer::GradientCache::mixFloats, // colors are stored in linear
};
ChannelMixer mix = gMixers[mUseFloatTexture];
- GradientColor start;
- (this->*split)(colors[0], start);
+ FloatColor start;
+ start.setSRGB(colors[0]);
- GradientColor end;
- (this->*split)(colors[1], end);
+ FloatColor end;
+ end.setSRGB(colors[1]);
int currentPos = 1;
float startPos = positions[0];
@@ -255,7 +241,7 @@
currentPos++;
- (this->*split)(colors[currentPos], end);
+ end.setSRGB(colors[currentPos]);
distance = positions[currentPos] - startPos;
}
@@ -266,10 +252,10 @@
memcpy(pixels + rowBytes, pixels, rowBytes);
if (mUseFloatTexture) {
- // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels);
} else {
- texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+ GLint internalFormat = mHasSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA;
+ texture->upload(internalFormat, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
texture->setFilter(GL_LINEAR);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 49be19a..5e35435 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -26,6 +26,8 @@
#include <utils/LruCache.h>
#include <utils/Mutex.h>
+#include "FloatColor.h"
+
namespace android {
namespace uirenderer {
@@ -150,25 +152,15 @@
void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info);
size_t bytesPerPixel() const;
+ size_t sourceBytesPerPixel() const;
- struct GradientColor {
- float r;
- float g;
- float b;
- float a;
- };
-
- typedef void (GradientCache::*ChannelSplitter)(uint32_t inColor,
- GradientColor& outColor) const;
-
- void splitToBytes(uint32_t inColor, GradientColor& outColor) const;
- void splitToFloats(uint32_t inColor, GradientColor& outColor) const;
-
- typedef void (GradientCache::*ChannelMixer)(GradientColor& start, GradientColor& end,
+ typedef void (GradientCache::*ChannelMixer)(const FloatColor& start, const FloatColor& end,
float amount, uint8_t*& dst) const;
- void mixBytes(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const;
- void mixFloats(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const;
+ void mixBytes(const FloatColor& start, const FloatColor& end,
+ float amount, uint8_t*& dst) const;
+ void mixFloats(const FloatColor& start, const FloatColor& end,
+ float amount, uint8_t*& dst) const;
LruCache<GradientCacheEntry, Texture*> mCache;
@@ -178,6 +170,7 @@
GLint mMaxTextureSize;
bool mUseFloatTexture;
bool mHasNpot;
+ bool mHasSRGB;
mutable Mutex mLock;
}; // class GradientCache
diff --git a/libs/hwui/IProfileRenderer.h b/libs/hwui/IProfileRenderer.h
new file mode 100644
index 0000000..947ed34
--- /dev/null
+++ b/libs/hwui/IProfileRenderer.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkPaint.h"
+
+namespace android {
+namespace uirenderer {
+
+class IProfileRenderer {
+public:
+ virtual void drawRect(float left, float top, float right, float bottom,
+ const SkPaint& paint) = 0;
+ virtual void drawRects(const float* rects, int count, const SkPaint& paint) = 0;
+ virtual uint32_t getViewportWidth() = 0;
+ virtual uint32_t getViewportHeight() = 0;
+
+ virtual ~IProfileRenderer() {}
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index c688a96..9874ce2 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -28,7 +28,7 @@
#include <ui/Region.h>
#include <SkPaint.h>
-#include <SkXfermode.h>
+#include <SkBlendMode.h>
#include "Matrix.h"
#include "Rect.h"
@@ -75,7 +75,7 @@
}
void setSize(uint32_t width, uint32_t height) {
- texture.updateSize(width, height, texture.format());
+ texture.updateSize(width, height, texture.internalFormat(), texture.format());
}
inline void setBlend(bool blend) {
@@ -98,7 +98,7 @@
this->alpha = alpha;
}
- inline void setAlpha(int alpha, SkXfermode::Mode mode) {
+ inline void setAlpha(int alpha, SkBlendMode mode) {
this->alpha = alpha;
this->mode = mode;
}
@@ -107,7 +107,7 @@
return alpha;
}
- inline SkXfermode::Mode getMode() const {
+ inline SkBlendMode getMode() const {
return mode;
}
@@ -208,7 +208,7 @@
/**
* Blending mode of the layer.
*/
- SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
+ SkBlendMode mode = SkBlendMode::kSrcOver;
/**
* Optional texture coordinates transform.
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index 66413dc..c5d5492 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -274,7 +274,7 @@
// One or more unclipped saveLayers have been enqueued, with deferred clears.
// Flush all of these clears with a single draw
SkPaint* paint = allocator.create<SkPaint>();
- paint->setXfermodeMode(SkXfermode::kClear_Mode);
+ paint->setBlendMode(SkBlendMode::kClear);
SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>(bounds,
Matrix4::identity(), nullptr, paint,
verts, vertCount);
diff --git a/libs/hwui/NinePatchUtils.h b/libs/hwui/NinePatchUtils.h
new file mode 100644
index 0000000..7a271b7
--- /dev/null
+++ b/libs/hwui/NinePatchUtils.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+namespace android {
+namespace NinePatchUtils {
+
+static inline void SetLatticeDivs(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--;
+ }
+}
+
+}; // namespace NinePatchUtils
+}; // namespace android
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index a6c281d..52c62cc 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -225,17 +225,15 @@
static const UvMapper sIdentity;
-const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
- const uint32_t bitmapWidth, const uint32_t bitmapHeight,
+const Patch* PatchCache::get( const uint32_t bitmapWidth, const uint32_t bitmapHeight,
const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch);
const Patch* mesh = mCache.get(description);
if (!mesh) {
- const UvMapper& mapper = entry ? entry->uvMapper : sIdentity;
Patch* newMesh = new Patch(bitmapWidth, bitmapHeight,
- pixelWidth, pixelHeight, mapper, patch);
+ pixelWidth, pixelHeight, sIdentity, patch);
if (newMesh->vertices) {
setupMesh(newMesh);
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index 6e6a730..0624c35 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -22,7 +22,6 @@
#include <androidfw/ResourceTypes.h>
-#include "AssetAtlas.h"
#include "Debug.h"
#include "utils/Pair.h"
@@ -54,8 +53,7 @@
explicit PatchCache(RenderState& renderState);
~PatchCache();
- const Patch* get(const AssetAtlas::Entry* entry,
- const uint32_t bitmapWidth, const uint32_t bitmapHeight,
+ const Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch);
void clear();
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index eb606cb..d46c46f93 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -17,6 +17,8 @@
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkColor.h>
+#include <SkColorFilter.h>
+#include <SkMaskFilter.h>
#include <SkPaint.h>
#include <SkPath.h>
#include <SkPathEffect.h>
@@ -149,8 +151,7 @@
paint.setColorFilter(nullptr);
paint.setMaskFilter(nullptr);
paint.setShader(nullptr);
- SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
- SkSafeUnref(paint.setXfermode(mode));
+ paint.setBlendMode(SkBlendMode::kSrc);
}
static SkBitmap* drawPath(const SkPath* path, const SkPaint* paint, PathTexture* texture,
@@ -182,8 +183,7 @@
PathCache::PathCache()
: mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity)
, mSize(0)
- , mMaxSize(Properties::pathCacheSize)
- , mTexNum(0) {
+ , mMaxSize(Properties::pathCacheSize) {
mCache.setOnEntryRemovedListener(this);
GLint maxTextureSize;
@@ -239,7 +239,6 @@
"the cache in an inconsistent state", size);
}
mSize -= size;
- mTexNum--;
}
PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d",
@@ -264,7 +263,14 @@
}
void PathCache::trim() {
- while (mSize > mMaxSize || mTexNum > DEFAULT_PATH_TEXTURE_CAP) {
+ // 25 is just an arbitrary lower bound to ensure we aren't in weird edge cases
+ // of things like a cap of 0 or 1 as that's going to break things.
+ // It does not represent a reasonable minimum value
+ static_assert(DEFAULT_PATH_TEXTURE_CAP > 25, "Path cache texture cap is too small");
+
+ while (mSize > mMaxSize || mCache.size() > DEFAULT_PATH_TEXTURE_CAP) {
+ LOG_ALWAYS_FATAL_IF(!mCache.size(), "Inconsistent mSize! Ran out of items to remove!"
+ " mSize = %u, mMaxSize = %u", mSize, mMaxSize);
mCache.removeOldest();
}
}
@@ -312,7 +318,6 @@
ATRACE_NAME("Upload Path Texture");
texture->upload(bitmap);
texture->setFilter(GL_LINEAR);
- mTexNum++;
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index b45a2c5..18bcc56 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -281,12 +281,6 @@
bool mDebugEnabled;
- /**
- * Driver allocated 4k/8k/16k memory for small path cache,
- * limit the number of PathTexture in case occupy too much memory in hardware.
- */
- uint32_t mTexNum;
-
sp<PathProcessor> mProcessor;
std::vector<uint32_t> mGarbage;
diff --git a/libs/hwui/ProfileRenderer.cpp b/libs/hwui/ProfileRenderer.cpp
new file mode 100644
index 0000000..0ad484c
--- /dev/null
+++ b/libs/hwui/ProfileRenderer.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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 "ProfileRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+void ProfileRenderer::drawRect(float left, float top, float right, float bottom,
+ const SkPaint& paint) {
+ mRenderer.drawRect(left, top, right, bottom, &paint);
+}
+
+void ProfileRenderer::drawRects(const float* rects, int count, const SkPaint& paint) {
+ mRenderer.drawRects(rects, count, &paint);
+}
+
+uint32_t ProfileRenderer::getViewportWidth() {
+ return mRenderer.getViewportWidth();
+}
+
+uint32_t ProfileRenderer::getViewportHeight() {
+ return mRenderer.getViewportHeight();
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/ProfileRenderer.h b/libs/hwui/ProfileRenderer.h
new file mode 100644
index 0000000..b9e586f
--- /dev/null
+++ b/libs/hwui/ProfileRenderer.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "IProfileRenderer.h"
+
+#include "BakedOpRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+class ProfileRenderer : public IProfileRenderer {
+public:
+ ProfileRenderer(BakedOpRenderer& renderer)
+ : mRenderer(renderer) {
+ }
+
+ void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
+ void drawRects(const float* rects, int count, const SkPaint& paint) override;
+ uint32_t getViewportWidth() override;
+ uint32_t getViewportHeight() override;
+
+ virtual ~ProfileRenderer() {}
+
+private:
+ BakedOpRenderer& mRenderer;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index e5200a5..f5beb62 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -85,6 +85,8 @@
#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42
#define PROGRAM_HAS_ROUND_RECT_CLIP 43
+#define PROGRAM_HAS_GAMMA_CORRECTION 44
+
///////////////////////////////////////////////////////////////////////////////
// Types
///////////////////////////////////////////////////////////////////////////////
@@ -158,6 +160,8 @@
bool hasDebugHighlight;
bool hasRoundRectClip;
+ bool hasGammaCorrection;
+
/**
* Resets this description. All fields are reset back to the default
* values they hold after building a new instance.
@@ -196,6 +200,8 @@
hasDebugHighlight = false;
hasRoundRectClip = false;
+
+ hasGammaCorrection = false;
}
/**
@@ -262,6 +268,7 @@
if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP;
+ if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
return key;
}
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 59225e1..4ef6b85 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -17,8 +17,8 @@
#include <utils/String8.h>
#include "Caches.h"
-#include "Dither.h"
#include "ProgramCache.h"
+#include "Properties.h"
namespace android {
namespace uirenderer {
@@ -69,22 +69,16 @@
"varying highp vec2 outBitmapTexCoords;\n";
const char* gVS_Header_Varyings_HasGradient[6] = {
// Linear
- "varying highp vec2 linear;\n"
- "varying vec2 ditherTexCoords;\n",
- "varying float linear;\n"
- "varying vec2 ditherTexCoords;\n",
+ "varying highp vec2 linear;\n",
+ "varying float linear;\n",
// Circular
- "varying highp vec2 circular;\n"
- "varying vec2 ditherTexCoords;\n",
- "varying highp vec2 circular;\n"
- "varying vec2 ditherTexCoords;\n",
+ "varying highp vec2 circular;\n",
+ "varying highp vec2 circular;\n",
// Sweep
- "varying highp vec2 sweep;\n"
- "varying vec2 ditherTexCoords;\n",
- "varying highp vec2 sweep;\n"
- "varying vec2 ditherTexCoords;\n",
+ "varying highp vec2 sweep;\n",
+ "varying highp vec2 sweep;\n",
};
const char* gVS_Header_Varyings_HasRoundRectClip =
"varying highp vec2 roundRectPos;\n";
@@ -98,22 +92,16 @@
" outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n";
const char* gVS_Main_OutGradient[6] = {
// Linear
- " linear = vec2((screenSpace * position).x, 0.5);\n"
- " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
- " linear = (screenSpace * position).x;\n"
- " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
+ " linear = vec2((screenSpace * position).x, 0.5);\n",
+ " linear = (screenSpace * position).x;\n",
// Circular
- " circular = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
- " circular = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
+ " circular = (screenSpace * position).xy;\n",
+ " circular = (screenSpace * position).xy;\n",
// Sweep
+ " sweep = (screenSpace * position).xy;\n",
" sweep = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
- " sweep = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
};
const char* gVS_Main_OutBitmapTexCoords =
" outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n";
@@ -147,12 +135,11 @@
"uniform sampler2D baseSampler;\n";
const char* gFS_Uniforms_ExternalTextureSampler =
"uniform samplerExternalOES baseSampler;\n";
-const char* gFS_Uniforms_Dither =
- "uniform sampler2D ditherSampler;";
const char* gFS_Uniforms_GradientSampler[2] = {
- "%s\n"
+ "uniform vec2 screenSize;\n"
"uniform sampler2D gradientSampler;\n",
- "%s\n"
+
+ "uniform vec2 screenSize;\n"
"uniform vec4 startColor;\n"
"uniform vec4 endColor;\n"
};
@@ -172,18 +159,57 @@
"uniform vec4 roundRectInnerRectLTRB;\n"
"uniform float roundRectRadius;\n";
+// Dithering must be done in the quantization space
+// When we are writing to an sRGB framebuffer, we must do the following:
+// EOCF(OECF(color) + dither)
+// We approximate the transfer functions with gamma 2.0 to avoid branches and pow()
+// The dithering pattern is generated with a triangle noise generator in the range [-0.0,1.0]
+// TODO: Handle linear fp16 render targets
+const char* gFS_Gradient_Functions =
+ "\nfloat triangleNoise(const highp vec2 n) {\n"
+ " highp vec2 p = fract(n * vec2(5.3987, 5.4421));\n"
+ " p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));\n"
+ " highp float xy = p.x * p.y;\n"
+ " return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
+ "}\n";
+const char* gFS_Gradient_Preamble[2] = {
+ // Linear framebuffer
+ "\nvec4 dither(const vec4 color) {\n"
+ " return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);"
+ "}\n"
+ "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n"
+ " return pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));"
+ "}\n",
+ // sRGB framebuffer
+ "\nvec4 dither(const vec4 color) {\n"
+ " vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0);\n"
+ " return vec4(dithered * dithered, color.a);\n"
+ "}\n"
+ "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n"
+ " return mix(a, b, v);"
+ "}\n"
+};
+
+// Uses luminance coefficients from Rec.709 to choose the appropriate gamma
+// The gamma() function assumes that bright text will be displayed on a dark
+// background and that dark text will be displayed on bright background
+// The gamma coefficient is chosen to thicken or thin the text accordingly
+// The dot product used to compute the luminance could be approximated with
+// a simple max(color.r, color.g, color.b)
+const char* gFS_Gamma_Preamble =
+ "\n#define GAMMA (%.2f)\n"
+ "#define GAMMA_INV (%.2f)\n"
+ "\nfloat gamma(float a, const vec3 color) {\n"
+ " float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n"
+ " return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);\n"
+ "}\n";
+
const char* gFS_Main =
"\nvoid main(void) {\n"
- " lowp vec4 fragColor;\n";
+ " vec4 fragColor;\n";
-const char* gFS_Main_Dither[2] = {
- // ES 2.0
- "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE),
- // ES 3.0
- "texture2D(ditherSampler, ditherTexCoords).a"
-};
-const char* gFS_Main_AddDitherToGradient =
- " gradientColor += %s;\n";
+const char* gFS_Main_AddDither =
+ " fragColor = dither(fragColor);\n";
// Fast cases
const char* gFS_Fast_SingleColor =
@@ -202,24 +228,32 @@
"\nvoid main(void) {\n"
" gl_FragColor = texture2D(baseSampler, outTexCoords);\n"
"}\n\n";
+const char* gFS_Fast_SingleA8Texture_ApplyGamma =
+ "\nvoid main(void) {\n"
+ " gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, GAMMA));\n"
+ "}\n\n";
const char* gFS_Fast_SingleModulateA8Texture =
"\nvoid main(void) {\n"
" gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n"
"}\n\n";
+const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
+ "\nvoid main(void) {\n"
+ " gl_FragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n"
+ "}\n\n";
const char* gFS_Fast_SingleGradient[2] = {
"\nvoid main(void) {\n"
- " gl_FragColor = %s + texture2D(gradientSampler, linear);\n"
+ " gl_FragColor = dither(texture2D(gradientSampler, linear));\n"
"}\n\n",
"\nvoid main(void) {\n"
- " gl_FragColor = %s + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+ " gl_FragColor = dither(gammaMix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n"
"}\n\n",
};
const char* gFS_Fast_SingleModulateGradient[2] = {
"\nvoid main(void) {\n"
- " gl_FragColor = %s + color.a * texture2D(gradientSampler, linear);\n"
+ " gl_FragColor = dither(color.a * texture2D(gradientSampler, linear));\n"
"}\n\n",
"\nvoid main(void) {\n"
- " gl_FragColor = %s + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+ " gl_FragColor = dither(color.a * gammaMix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n"
"}\n\n"
};
@@ -239,29 +273,31 @@
// Modulate
" fragColor = color * texture2D(baseSampler, outTexCoords);\n"
};
-const char* gFS_Main_FetchA8Texture[2] = {
+const char* gFS_Main_FetchA8Texture[4] = {
// Don't modulate
" fragColor = texture2D(baseSampler, outTexCoords);\n",
+ " fragColor = texture2D(baseSampler, outTexCoords);\n",
// Modulate
" fragColor = color * texture2D(baseSampler, outTexCoords).a;\n",
+ " fragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n",
};
const char* gFS_Main_FetchGradient[6] = {
// Linear
" vec4 gradientColor = texture2D(gradientSampler, linear);\n",
- " vec4 gradientColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n",
+ " vec4 gradientColor = gammaMix(startColor, endColor, clamp(linear, 0.0, 1.0));\n",
// Circular
" vec4 gradientColor = texture2D(gradientSampler, vec2(length(circular), 0.5));\n",
- " vec4 gradientColor = mix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n",
+ " vec4 gradientColor = gammaMix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n",
// Sweep
" highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
" vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n",
" highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
- " vec4 gradientColor = mix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n"
+ " vec4 gradientColor = gammaMix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n"
};
const char* gFS_Main_FetchBitmap =
" vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
@@ -271,29 +307,38 @@
" fragColor = blendShaders(gradientColor, bitmapColor)";
const char* gFS_Main_BlendShadersGB =
" fragColor = blendShaders(bitmapColor, gradientColor)";
-const char* gFS_Main_BlendShaders_Modulate[3] = {
+const char* gFS_Main_BlendShaders_Modulate[6] = {
// Don't modulate
";\n",
+ ";\n",
// Modulate
" * color.a;\n",
+ " * color.a;\n",
// Modulate with alpha 8 texture
" * texture2D(baseSampler, outTexCoords).a;\n",
+ " * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n",
};
-const char* gFS_Main_GradientShader_Modulate[3] = {
+const char* gFS_Main_GradientShader_Modulate[6] = {
// Don't modulate
" fragColor = gradientColor;\n",
+ " fragColor = gradientColor;\n",
// Modulate
" fragColor = gradientColor * color.a;\n",
+ " fragColor = gradientColor * color.a;\n",
// Modulate with alpha 8 texture
" fragColor = gradientColor * texture2D(baseSampler, outTexCoords).a;\n",
+ " fragColor = gradientColor * gamma(texture2D(baseSampler, outTexCoords).a, gradientColor.rgb);\n",
};
-const char* gFS_Main_BitmapShader_Modulate[3] = {
+const char* gFS_Main_BitmapShader_Modulate[6] = {
// Don't modulate
" fragColor = bitmapColor;\n",
+ " fragColor = bitmapColor;\n",
// Modulate
" fragColor = bitmapColor * color.a;\n",
+ " fragColor = bitmapColor * color.a;\n",
// Modulate with alpha 8 texture
" fragColor = bitmapColor * texture2D(baseSampler, outTexCoords).a;\n",
+ " fragColor = bitmapColor * gamma(texture2D(baseSampler, outTexCoords).a, bitmapColor.rgb);\n",
};
const char* gFS_Main_FragColor =
" gl_FragColor = fragColor;\n";
@@ -385,7 +430,8 @@
///////////////////////////////////////////////////////////////////////////////
ProgramCache::ProgramCache(Extensions& extensions)
- : mHasES3(extensions.getMajorGlVersion() >= 3) {
+ : mHasES3(extensions.getMajorGlVersion() >= 3)
+ , mHasSRGB(extensions.hasSRGB()) {
}
ProgramCache::~ProgramCache() {
@@ -518,6 +564,7 @@
static bool shaderOp(const ProgramDescription& description, String8& shader,
const int modulateOp, const char** snippets) {
int op = description.hasAlpha8Texture ? MODULATE_OP_MODULATE_A8 : modulateOp;
+ op = op * 2 + description.hasGammaCorrection;
shader.append(snippets[op]);
return description.hasAlpha8Texture;
}
@@ -570,13 +617,16 @@
shader.append(gFS_Uniforms_ExternalTextureSampler);
}
if (description.hasGradient) {
- shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient],
- gFS_Uniforms_Dither);
+ shader.append(gFS_Uniforms_GradientSampler[description.isSimpleGradient]);
}
if (description.hasRoundRectClip) {
shader.append(gFS_Uniforms_HasRoundRectClip);
}
+ if (description.hasGammaCorrection) {
+ shader.appendFormat(gFS_Gamma_Preamble, Properties::textGamma, 1.0f / Properties::textGamma);
+ }
+
// Optimization for common cases
if (!description.hasVertexAlpha
&& !blendFramebuffer
@@ -607,18 +657,26 @@
fast = true;
} else if (singleA8Texture) {
if (!description.modulate) {
- shader.append(gFS_Fast_SingleA8Texture);
+ if (description.hasGammaCorrection) {
+ shader.append(gFS_Fast_SingleA8Texture_ApplyGamma);
+ } else {
+ shader.append(gFS_Fast_SingleA8Texture);
+ }
} else {
- shader.append(gFS_Fast_SingleModulateA8Texture);
+ if (description.hasGammaCorrection) {
+ shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma);
+ } else {
+ shader.append(gFS_Fast_SingleModulateA8Texture);
+ }
}
fast = true;
} else if (singleGradient) {
+ shader.append(gFS_Gradient_Functions);
+ shader.append(gFS_Gradient_Preamble[mHasSRGB]);
if (!description.modulate) {
- shader.appendFormat(gFS_Fast_SingleGradient[description.isSimpleGradient],
- gFS_Main_Dither[mHasES3]);
+ shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]);
} else {
- shader.appendFormat(gFS_Fast_SingleModulateGradient[description.isSimpleGradient],
- gFS_Main_Dither[mHasES3]);
+ shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]);
}
fast = true;
}
@@ -652,6 +710,10 @@
if (description.isBitmapNpot) {
generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
}
+ if (description.hasGradient) {
+ shader.append(gFS_Gradient_Functions);
+ shader.append(gFS_Gradient_Preamble[mHasSRGB]);
+ }
// Begin the shader
shader.append(gFS_Main); {
@@ -659,7 +721,8 @@
if (description.hasTexture || description.hasExternalTexture) {
if (description.hasAlpha8Texture) {
if (!description.hasGradient && !description.hasBitmap) {
- shader.append(gFS_Main_FetchA8Texture[modulateOp]);
+ shader.append(
+ gFS_Main_FetchA8Texture[modulateOp * 2 + description.hasGammaCorrection]);
}
} else {
shader.append(gFS_Main_FetchTexture[modulateOp]);
@@ -671,7 +734,6 @@
}
if (description.hasGradient) {
shader.append(gFS_Main_FetchGradient[gradientIndex(description)]);
- shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]);
}
if (description.hasBitmap) {
if (!description.isBitmapNpot) {
@@ -715,6 +777,10 @@
}
}
+ if (description.hasGradient) {
+ shader.append(gFS_Main_AddDither);
+ }
+
// Output the fragment
if (!blendFramebuffer) {
shader.append(gFS_Main_FragColor);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 9ac885b..292ecdf 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -59,6 +59,7 @@
std::map<programid, std::unique_ptr<Program>> mCache;
const bool mHasES3;
+ const bool mHasSRGB;
}; // class ProgramCache
}; // namespace uirenderer
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 93b2e15..848161e 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -214,7 +214,7 @@
property_get(PROPERTY_DEFAULT_RENDERER, prop, "opengl");
if (!strcmp(prop, "skiagl") ) {
sRenderPipelineType = RenderPipelineType::SkiaGL;
- } else if (!strcmp(prop, "skiavulkan") ) {
+ } else if (!strcmp(prop, "skiavk") ) {
sRenderPipelineType = RenderPipelineType::SkiaVulkan;
} else { //"opengl"
sRenderPipelineType = RenderPipelineType::OpenGL;
@@ -222,5 +222,11 @@
return sRenderPipelineType;
}
+bool Properties::isSkiaEnabled() {
+ auto renderType = getRenderPipelineType();
+ return RenderPipelineType::SkiaGL == renderType
+ || RenderPipelineType::SkiaVulkan == renderType;
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index eedc9e7..b4a3118 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -203,7 +203,7 @@
#define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width"
#define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height"
-// Gamma (>= 1.0, <= 10.0)
+// Gamma (>= 1.0, <= 3.0)
#define PROPERTY_TEXT_GAMMA "hwui.text_gamma"
///////////////////////////////////////////////////////////////////////////////
@@ -222,7 +222,7 @@
#define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f
-#define DEFAULT_TEXT_GAMMA 1.4f
+#define DEFAULT_TEXT_GAMMA 1.45f // Match design tools
// cap to 256 to limite paths in the path cache
#define DEFAULT_PATH_TEXTURE_CAP 256
@@ -308,6 +308,7 @@
static ProfileType getProfileType();
static RenderPipelineType getRenderPipelineType();
+ static bool isSkiaEnabled();
// Should be used only by test apps
static bool waitForGpuCompletion;
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index 38fb70a..e3258e3 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -46,8 +46,17 @@
void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) {
if (mOneShotListener.get()) {
- mOneShotListener->onAnimationFinished(animator);
+ sp<AnimationListener> listener = std::move(mOneShotListener);
+ // Set the listener to nullptr before the onAnimationFinished callback, rather than after,
+ // for two reasons:
+ // 1) We need to prevent changes to mOneShotListener during the onAnimationFinished
+ // callback (specifically in AnimationListenerBridge::onAnimationFinished(...) from
+ // triggering dtor of the bridge and potentially unsafely re-entering
+ // AnimationListenerBridge::onAnimationFinished(...).
+ // 2) It's possible that there are changes to the listener during the callback, therefore
+ // we need to reset the listener before the callback rather than afterwards.
mOneShotListener = nullptr;
+ listener->onAnimationFinished(animator);
}
}
diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp
index 6ba0ab5..2a03e6a 100644
--- a/libs/hwui/PropertyValuesHolder.cpp
+++ b/libs/hwui/PropertyValuesHolder.cpp
@@ -16,6 +16,7 @@
#include "PropertyValuesHolder.h"
+#include "utils/Color.h"
#include "utils/VectorDrawableUtils.h"
#include <utils/Log.h>
@@ -25,18 +26,26 @@
using namespace VectorDrawable;
-inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
- return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
+inline constexpr float lerp(float fromValue, float toValue, float fraction) {
+ return float (fromValue * (1 - fraction) + toValue * fraction);
+}
+
+inline constexpr float linearize(U8CPU component) {
+ return EOCF_sRGB(component / 255.0f);
}
// TODO: Add a test for this
void ColorEvaluator::evaluate(SkColor* outColor,
const SkColor& fromColor, const SkColor& toColor, float fraction) const {
- U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
- U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
- U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
- U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
- *outColor = SkColorSetARGB(alpha, red, green, blue);
+ float a = lerp(SkColorGetA(fromColor) / 255.0f, SkColorGetA(toColor) / 255.0f, fraction);
+ float r = lerp(linearize(SkColorGetR(fromColor)), linearize(SkColorGetR(toColor)), fraction);
+ float g = lerp(linearize(SkColorGetG(fromColor)), linearize(SkColorGetG(toColor)), fraction);
+ float b = lerp(linearize(SkColorGetB(fromColor)), linearize(SkColorGetB(toColor)), fraction);
+ *outColor = SkColorSetARGB(
+ (U8CPU) roundf(a * 255.0f),
+ (U8CPU) roundf(OECF_sRGB(r) * 255.0f),
+ (U8CPU) roundf(OECF_sRGB(g) * 255.0f),
+ (U8CPU) roundf(OECF_sRGB(b) * 255.0f));
}
void PathEvaluator::evaluate(PathData* out,
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index ddca122..22c6dfc 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -197,7 +197,7 @@
Texture sourceTexture(caches);
sourceTexture.wrap(sourceTexId,
- sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */);
+ sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0, 0 /* total lie */);
CopyResult copyResult = copyTextureInto(caches, renderThread.renderState(),
sourceTexture, texTransform, srcRect, bitmap);
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index a65c22c..f9a7c36f2 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -26,7 +26,6 @@
#include "Vector.h"
#include <androidfw/ResourceTypes.h>
-#include <SkXfermode.h>
class SkBitmap;
class SkPaint;
@@ -212,15 +211,14 @@
};
struct BitmapOp : RecordedOp {
- BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
+ BitmapOp(BASE_PARAMS, Bitmap* bitmap)
: SUPER(BitmapOp)
, bitmap(bitmap) {}
- const SkBitmap* bitmap;
- // TODO: asset atlas/texture id lookup?
+ 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)
@@ -228,7 +226,7 @@
, meshHeight(meshHeight)
, vertices(vertices)
, colors(colors) {}
- const SkBitmap* bitmap;
+ Bitmap* bitmap;
const int meshWidth;
const int meshHeight;
const float* vertices;
@@ -236,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;
};
@@ -258,12 +256,12 @@
struct ColorOp : RecordedOp {
// Note: unbounded op that will fillclip, so no bounds/matrix needed
- ColorOp(const ClipBase* localClip, int color, SkXfermode::Mode mode)
+ ColorOp(const ClipBase* localClip, int color, SkBlendMode mode)
: RecordedOp(RecordedOpId::ColorOp, Rect(), Matrix4::identity(), localClip, nullptr)
, color(color)
, mode(mode) {}
const int color;
- const SkXfermode::Mode mode;
+ const SkBlendMode mode;
};
struct FunctorOp : RecordedOp {
@@ -290,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;
};
@@ -505,7 +503,7 @@
: SUPER_PAINTLESS(LayerOp)
, layerHandle(layerHandle)
, alpha(paint ? paint->getAlpha() / 255.0f : 1.0f)
- , mode(PaintUtils::getXfermodeDirect(paint))
+ , mode(PaintUtils::getBlendModeDirect(paint))
, colorFilter(paint ? paint->getColorFilter() : nullptr) {}
explicit LayerOp(RenderNode& node)
@@ -519,7 +517,7 @@
// constructed until after this operation is constructed.
OffscreenBuffer** layerHandle;
const float alpha;
- const SkXfermode::Mode mode;
+ const SkBlendMode mode;
// pointer to object owned by either LayerProperties, or a recorded Paint object in a
// BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 27e6a12..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();
@@ -247,7 +247,7 @@
// ----------------------------------------------------------------------------
// android/graphics/Canvas draw operations
// ----------------------------------------------------------------------------
-void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
+void RecordingCanvas::drawColor(int color, SkBlendMode mode) {
addOp(alloc().create_trivial<ColorOp>(
getRecordedClip(),
color,
@@ -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 efa6b91..b6031c4 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -22,10 +22,10 @@
#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"
-#include "utils/NinePatch.h"
#include <SkDrawFilter.h>
#include <SkPaint.h>
@@ -50,7 +50,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
@@ -144,7 +144,7 @@
// ----------------------------------------------------------------------------
// android/graphics/Canvas draw operations
// ----------------------------------------------------------------------------
- virtual void drawColor(int color, SkXfermode::Mode mode) override;
+ virtual void drawColor(int color, SkBlendMode mode) override;
virtual void drawPaint(const SkPaint& paint) override;
// Geometry
@@ -176,15 +176,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 +203,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 +285,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/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index dd20a76..a03ded6 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -51,7 +51,7 @@
RenderNode::~RenderNode() {
deleteDisplayList(nullptr);
delete mStagingDisplayList;
- LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
+ LOG_ALWAYS_FATAL_IF(hasLayer(), "layer missed detachment!");
}
void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) {
@@ -81,7 +81,7 @@
<< (properties().hasShadow() ? ", casting shadow" : "")
<< (isRenderable() ? "" : ", empty")
<< (properties().getProjectBackwards() ? ", projected" : "")
- << (mLayer != nullptr ? ", on HW Layer" : "")
+ << (hasLayer() ? ", on HW Layer" : "")
<< ")" << std::endl;
properties().debugOutputProperties(output, level + 1);
@@ -229,19 +229,6 @@
}
}
-static OffscreenBuffer* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
- return renderState.layerPool().get(renderState, width, height);
-}
-
-static void destroyLayer(OffscreenBuffer* layer) {
- RenderState& renderState = layer->renderState;
- renderState.layerPool().putOrDelete(layer);
-}
-
-static bool layerMatchesWidthAndHeight(OffscreenBuffer* layer, int width, int height) {
- return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height;
-}
-
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
@@ -250,36 +237,17 @@
|| CC_UNLIKELY(!isRenderable())
|| CC_UNLIKELY(properties().getWidth() == 0)
|| CC_UNLIKELY(properties().getHeight() == 0)) {
- if (CC_UNLIKELY(mLayer)) {
- destroyLayer(mLayer);
- mLayer = nullptr;
+ if (CC_UNLIKELY(hasLayer())) {
+ renderthread::CanvasContext::destroyLayer(this);
}
return;
}
- bool transformUpdateNeeded = false;
- if (!mLayer) {
- mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+ if(info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator)) {
damageSelf(info);
- transformUpdateNeeded = true;
- } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
- // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering)
- // Or, ideally, maintain damage between frames on node/layer so ordering is always correct
- RenderState& renderState = mLayer->renderState;
- if (properties().fitsOnLayer()) {
- mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight());
- } else {
- destroyLayer(mLayer);
- mLayer = nullptr;
- }
- damageSelf(info);
- transformUpdateNeeded = true;
}
- SkRect dirty;
- info.damageAccumulator->peekAtDirty(&dirty);
-
- if (!mLayer) {
+ if (!hasLayer()) {
Caches::getInstance().dumpMemoryUsage();
if (info.errorHandler) {
std::ostringstream err;
@@ -296,13 +264,8 @@
return;
}
- if (transformUpdateNeeded && mLayer) {
- // update the transform in window of the layer to reset its origin wrt light source position
- Matrix4 windowTransform;
- info.damageAccumulator->computeCurrentTransform(&windowTransform);
- mLayer->setWindowTransform(windowTransform);
- }
-
+ SkRect dirty;
+ info.damageAccumulator->peekAtDirty(&dirty);
info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
// There might be prefetched layers that need to be accounted for.
@@ -332,9 +295,9 @@
bool willHaveFunctor = false;
if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) {
- willHaveFunctor = !mStagingDisplayList->getFunctors().empty();
+ willHaveFunctor = mStagingDisplayList->hasFunctor();
} else if (mDisplayList) {
- willHaveFunctor = !mDisplayList->getFunctors().empty();
+ willHaveFunctor = mDisplayList->hasFunctor();
}
bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
willHaveFunctor, functorsNeedLayer);
@@ -347,15 +310,15 @@
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(info);
}
- prepareSubTree(info, childFunctorsNeedLayer, mDisplayList);
if (mDisplayList) {
- for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) {
- // If any vector drawable in the display list needs update, damage the node.
- if (vectorDrawable->isDirty()) {
- damageSelf(info);
- }
- vectorDrawable->setPropertyChangeWillBeConsumed(true);
+ info.out.hasFunctors |= mDisplayList->hasFunctor();
+ bool isDirty = mDisplayList->prepareListAndChildren(info, childFunctorsNeedLayer,
+ [](RenderNode* child, TreeInfo& info, bool functorsNeedLayer) {
+ child->prepareTreeImpl(info, functorsNeedLayer);
+ });
+ if (isDirty) {
+ damageSelf(info);
}
}
pushLayerUpdate(info);
@@ -393,20 +356,15 @@
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
if (mStagingDisplayList) {
- for (auto&& child : mStagingDisplayList->getChildren()) {
- child->renderNode->incParentRefCount();
- }
+ mStagingDisplayList->updateChildren([](RenderNode* child) {
+ child->incParentRefCount();
+ });
}
deleteDisplayList(info ? info->observer : nullptr, info);
mDisplayList = mStagingDisplayList;
mStagingDisplayList = nullptr;
if (mDisplayList) {
- for (auto& iter : mDisplayList->getFunctors()) {
- (*iter.functor)(DrawGlInfo::kModeSync, nullptr);
- }
- for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) {
- vectorDrawable->syncProperties();
- }
+ mDisplayList->syncContents();
}
}
@@ -423,41 +381,24 @@
void RenderNode::deleteDisplayList(TreeObserver* observer, TreeInfo* info) {
if (mDisplayList) {
- for (auto&& child : mDisplayList->getChildren()) {
- child->renderNode->decParentRefCount(observer, info);
+ mDisplayList->updateChildren([observer, info](RenderNode* child) {
+ child->decParentRefCount(observer, info);
+ });
+ if (!mDisplayList->reuseDisplayList(this, info ? &info->canvasContext : nullptr)) {
+ delete mDisplayList;
}
}
- delete mDisplayList;
mDisplayList = nullptr;
}
-void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree) {
- if (subtree) {
- TextureCache& cache = Caches::getInstance().textureCache;
- info.out.hasFunctors |= subtree->getFunctors().size();
- for (auto&& bitmapResource : subtree->getBitmapResources()) {
- void* ownerToken = &info.canvasContext;
- info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource);
- }
- for (auto&& op : subtree->getChildren()) {
- RenderNode* childNode = op->renderNode;
- info.damageAccumulator->pushTransform(&op->localMatrix);
- bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
- childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
- info.damageAccumulator->popTransform();
- }
- }
-}
-
void RenderNode::destroyHardwareResources(TreeObserver* observer, TreeInfo* info) {
- if (mLayer) {
- destroyLayer(mLayer);
- mLayer = nullptr;
+ if (hasLayer()) {
+ renderthread::CanvasContext::destroyLayer(this);
}
if (mDisplayList) {
- for (auto&& child : mDisplayList->getChildren()) {
- child->renderNode->destroyHardwareResources(observer, info);
- }
+ mDisplayList->updateChildren([observer, info](RenderNode* child) {
+ child->destroyHardwareResources(observer, info);
+ });
if (mNeedsDisplayListSync) {
// Next prepare tree we are going to push a new display list, so we can
// drop our current one now
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index a0679b1..a05c744 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -32,6 +32,7 @@
#include "DisplayList.h"
#include "Matrix.h"
#include "RenderProperties.h"
+#include "SkiaDisplayList.h"
#include <vector>
@@ -39,6 +40,7 @@
class SkPaint;
class SkPath;
class SkRegion;
+class SkSurface;
namespace android {
namespace uirenderer {
@@ -48,6 +50,7 @@
class FrameBuilder;
class OffscreenBuffer;
class Rect;
+class SkiaDisplayList;
class SkiaShader;
struct RenderNodeOp;
@@ -199,6 +202,7 @@
}
OffscreenBuffer* getLayer() const { return mLayer; }
OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
+ void setLayer(OffscreenBuffer* layer) { mLayer = layer; }
// Note: The position callbacks are relying on the listener using
// the frameNumber to appropriately batch/synchronize these transactions.
@@ -239,7 +243,6 @@
void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);
- void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
void pushLayerUpdate(TreeInfo& info);
void deleteDisplayList(TreeObserver* observer, TreeInfo* info = nullptr);
@@ -284,6 +287,63 @@
uint32_t mParentCount;
sp<PositionListener> mPositionListener;
+
+// METHODS & FIELDS ONLY USED BY THE SKIA RENDERER
+public:
+ /**
+ * Detach and transfer ownership of an already allocated displayList for use
+ * in recording updated content for this renderNode
+ */
+ std::unique_ptr<SkiaDisplayList> detachAvailableList() {
+ return std::move(mAvailableDisplayList);
+ }
+
+ /**
+ * Attach unused displayList to this node for potential future reuse.
+ */
+ void attachAvailableList(SkiaDisplayList* skiaDisplayList) {
+ mAvailableDisplayList.reset(skiaDisplayList);
+ }
+
+ /**
+ * Returns true if an offscreen layer from any renderPipeline is attached
+ * to this node.
+ */
+ bool hasLayer() const { return mLayer || mLayerSurface.get(); }
+
+ /**
+ * Used by the RenderPipeline to attach an offscreen surface to the RenderNode.
+ * The surface is then will be used to store the contents of a layer.
+ */
+ void setLayerSurface(sk_sp<SkSurface> layer) { mLayerSurface = layer; }
+
+
+ /**
+ * If the RenderNode is of type LayerType::RenderLayer then this method will
+ * return the an offscreen rendering surface that is used to both render into
+ * the layer and composite the layer into its parent. If the type is not
+ * LayerType::RenderLayer then it will return a nullptr.
+ *
+ * NOTE: this function is only guaranteed to return accurate results after
+ * prepareTree has been run for this RenderNode
+ */
+ SkSurface* getLayerSurface() const { return mLayerSurface.get(); }
+
+private:
+ /**
+ * If this RenderNode has been used in a previous frame then the SkiaDisplayList
+ * from that frame is cached here until one of the following conditions is met:
+ * 1) The RenderNode is deleted (causing this to be deleted)
+ * 2) It is replaced with the displayList from the next completed frame
+ * 3) It is detached and used to to record a new displayList for a later frame
+ */
+ std::unique_ptr<SkiaDisplayList> mAvailableDisplayList;
+
+ /**
+ * An offscreen rendering target used to contain the contents this RenderNode
+ * when it has been set to draw as a LayerType::RenderLayer.
+ */
+ sk_sp<SkSurface> mLayerSurface;
}; // class RenderNode
} /* namespace uirenderer */
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index b0114bc..146fbe7 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -52,7 +52,7 @@
bool LayerProperties::setFromPaint(const SkPaint* paint) {
bool changed = false;
changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint)));
- changed |= setXferMode(PaintUtils::getXfermodeDirect(paint));
+ changed |= setXferMode(PaintUtils::getBlendModeDirect(paint));
changed |= setColorFilter(paint ? paint->getColorFilter() : nullptr);
return changed;
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 2f5223c..9ee2f9c 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -24,10 +24,10 @@
#include "utils/MathUtils.h"
#include "utils/PaintUtils.h"
+#include <SkBlendMode.h>
#include <SkCamera.h>
#include <SkMatrix.h>
#include <SkRegion.h>
-#include <SkXfermode.h>
#include <algorithm>
#include <stddef.h>
@@ -93,11 +93,11 @@
return mAlpha;
}
- bool setXferMode(SkXfermode::Mode mode) {
+ bool setXferMode(SkBlendMode mode) {
return RP_SET(mMode, mode);
}
- SkXfermode::Mode xferMode() const {
+ SkBlendMode xferMode() const {
return mMode;
}
@@ -133,7 +133,7 @@
// Whether or not that Layer's content is opaque, doesn't include alpha
bool mOpaque;
uint8_t mAlpha;
- SkXfermode::Mode mMode;
+ SkBlendMode mMode;
SkColorFilter* mColorFilter = nullptr;
};
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 9553ab4..c48b4dc 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -17,7 +17,9 @@
#include "SkiaCanvas.h"
#include "CanvasProperty.h"
+#include "NinePatchUtils.h"
#include "VectorDrawable.h"
+#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
#include <SkDrawable.h>
@@ -30,11 +32,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 +48,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 +86,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 +138,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 +153,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 +208,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 +274,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 +391,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 +399,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 +414,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();
}
@@ -386,7 +441,7 @@
// Canvas draw operations
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawColor(int color, SkXfermode::Mode mode) {
+void SkiaCanvas::drawColor(int color, SkBlendMode mode) {
mCanvas->drawColor(color, mode);
}
@@ -400,6 +455,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 +481,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 +550,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 +662,96 @@
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 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;
+ NinePatchUtils::SetLatticeDivs(&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 +766,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 +890,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 0e506f4..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");
}
@@ -100,7 +97,7 @@
virtual SkDrawFilter* getDrawFilter() override;
virtual void setDrawFilter(SkDrawFilter* drawFilter) override;
- virtual void drawColor(int color, SkXfermode::Mode mode) override;
+ virtual void drawColor(int color, SkBlendMode mode) override;
virtual void drawPaint(const SkPaint& paint) override;
virtual void drawPoint(float x, float y, const SkPaint& paint) override;
@@ -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/SkiaDisplayList.cpp b/libs/hwui/SkiaDisplayList.cpp
new file mode 100644
index 0000000..d10f306
--- /dev/null
+++ b/libs/hwui/SkiaDisplayList.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 "SkiaDisplayList.h"
+
+#include "renderthread/CanvasContext.h"
+#include "VectorDrawable.h"
+
+#include <SkImagePriv.h>
+#include <SkMutex.h>
+
+namespace android {
+namespace uirenderer {
+
+SkiaDisplayList::SkiaDisplayList(SkRect bounds) : mDrawable(SkLiteDL::New(bounds)) {
+ SkASSERT(projectionReceiveIndex == -1);
+}
+
+void SkiaDisplayList::syncContents() {
+ for (auto& functor : mChildFunctors) {
+ functor.syncFunctor();
+ }
+ for (auto& vectorDrawable : mVectorDrawables) {
+ vectorDrawable->syncProperties();
+ }
+}
+
+bool SkiaDisplayList::reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) {
+ reset(context ? context->getGrContext() : nullptr, SkRect::MakeEmpty());
+ node->attachAvailableList(this);
+ return true;
+}
+
+void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) {
+ for (auto& child : mChildNodes) {
+ updateFn(child.getRenderNode());
+ }
+}
+
+bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+ // force all mutable images to be pinned in the GPU cache for the duration
+ // of this frame
+ pinImages(info.canvasContext.getGrContext());
+
+ for (auto& child : mChildNodes) {
+ RenderNode* childNode = child.getRenderNode();
+ Matrix4 mat4(child.getRecordedMatrix());
+ info.damageAccumulator->pushTransform(&mat4);
+ // TODO: a layer is needed if the canvas is rotated or has a non-rect clip
+ bool childFunctorsNeedLayer = functorsNeedLayer;
+ childFn(childNode, info, childFunctorsNeedLayer);
+ info.damageAccumulator->popTransform();
+ }
+
+ bool isDirty = false;
+ for (auto& vectorDrawable : mVectorDrawables) {
+ // If any vector drawable in the display list needs update, damage the node.
+ if (vectorDrawable->isDirty()) {
+ isDirty = true;
+ }
+ vectorDrawable->setPropertyChangeWillBeConsumed(true);
+ }
+ return isDirty;
+}
+
+static std::vector<sk_sp<SkImage>> gPinnedImages;
+static SkBaseMutex gLock;
+
+void SkiaDisplayList::pinImages(GrContext* context) {
+ if (mPinnedImages) return;
+ for (SkImage* image : mMutableImages) {
+ SkImage_pinAsTexture(image, context);
+ }
+ mPinnedImages = true;
+}
+
+void SkiaDisplayList::unpinImages(GrContext* context) {
+ if (!mPinnedImages) return;
+ if (context) {
+ for (SkImage* image : mMutableImages) {
+ SkImage_unpinAsTexture(image, context);
+ }
+ } else {
+ gLock.acquire();
+ for (SkImage* image : mMutableImages) {
+ gPinnedImages.emplace_back(sk_ref_sp(image));
+ }
+ gLock.release();
+ }
+ mPinnedImages = false;
+}
+
+void SkiaDisplayList::cleanupImages(GrContext* context) {
+ gLock.acquire();
+ for (auto& image : gPinnedImages) {
+ SkImage_unpinAsTexture(image.get(), context);
+ }
+ gPinnedImages.clear();
+ gLock.release();
+}
+
+void SkiaDisplayList::reset(GrContext* context, SkRect bounds) {
+ unpinImages(context);
+ SkASSERT(!mPinnedImages);
+ mIsProjectionReceiver = false;
+
+ mDrawable->reset(bounds);
+
+ mMutableImages.clear();
+ mVectorDrawables.clear();
+ mChildFunctors.clear();
+ mChildNodes.clear();
+
+ projectionReceiveIndex = -1;
+ allocator.~LinearAllocator();
+ new (&allocator) LinearAllocator();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaDisplayList.h b/libs/hwui/SkiaDisplayList.h
new file mode 100644
index 0000000..c8a82bd
--- /dev/null
+++ b/libs/hwui/SkiaDisplayList.h
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "DisplayList.h"
+#include "SkiaDrawables.h"
+
+#include <deque>
+#include <SkLiteDL.h>
+#include <SkPictureRecorder.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * This class is intended to be self contained, but still subclasses from
+ * DisplayList to make it easier to support switching between the two at
+ * runtime. The downside of this inheritance is that we pay for the overhead
+ * of the parent class construction/destruction without any real benefit.
+ */
+class SkiaDisplayList : public DisplayList {
+public:
+ SkiaDisplayList(SkRect bounds);
+ virtual ~SkiaDisplayList() {
+ /* Given that we are using a LinearStdAllocator to store some of the
+ * SkDrawable contents we must ensure that any other object that is
+ * holding a reference to those drawables is destroyed prior to their
+ * deletion.
+ */
+ mDrawable.reset();
+ }
+
+ /**
+ * This resets the DisplayList so that it behaves as if the object were newly
+ * constructed with the provided bounds. The reuse avoids any overhead
+ * associated with destroying the SkLiteDL as well as the deques and vectors.
+ */
+ void reset(GrContext* context, SkRect bounds);
+
+ /**
+ * Use the linear allocator to create any SkDrawables needed by the display
+ * list. This could be dangerous as these objects are ref-counted, so we
+ * need to monitor that they don't extend beyond the lifetime of the class
+ * that creates them.
+ */
+ template<class T, typename... Params>
+ SkDrawable* allocateDrawable(Params&&... params) {
+ return allocator.create<T>(std::forward<Params>(params)...);
+ }
+
+ bool isSkiaDL() const override { return true; }
+
+ /**
+ * Returns true if the DisplayList does not have any recorded content
+ */
+ bool isEmpty() const override { return mDrawable->empty(); }
+
+ /**
+ * Returns true if this list directly contains a GLFunctor drawing command.
+ */
+ bool hasFunctor() const override { return !mChildFunctors.empty(); }
+
+ /**
+ * Returns true if this list directly contains a VectorDrawable drawing command.
+ */
+ bool hasVectorDrawables() const override { return !mVectorDrawables.empty(); }
+
+ /**
+ * Attempts to reset and reuse this DisplayList.
+ *
+ * @return true if the displayList will be reused and therefore should not be deleted
+ */
+ bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) override;
+
+ /**
+ * ONLY to be called by RenderNode::syncDisplayList so that we can notify any
+ * contained VectorDrawables or GLFunctors to sync their state.
+ *
+ * NOTE: This function can be folded into RenderNode when we no longer need
+ * to subclass from DisplayList
+ */
+ void syncContents() override;
+
+ /**
+ * ONLY to be called by RenderNode::prepareTree in order to prepare this
+ * list while the UI thread is blocked. Here we can upload mutable bitmaps
+ * and notify our parent if any of our content has been invalidated and in
+ * need of a redraw. If the renderNode has any children then they are also
+ * call in order to prepare them.
+ *
+ * @return true if any content change requires the node to be invalidated
+ *
+ * NOTE: This function can be folded into RenderNode when we no longer need
+ * to subclass from DisplayList
+ */
+
+ bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeInfo&, bool)> childFn) override;
+
+ /**
+ * Calls the provided function once for each child of this DisplayList
+ */
+ void updateChildren(std::function<void(RenderNode*)> updateFn) override;
+
+ /**
+ * Pin/Unpin any mutable images to the GPU cache. A pinned images is
+ * guaranteed to be remain in the cache until it has been unpinned which
+ * we leverage to avoid making a CPU copy of the pixels.
+ */
+ void pinImages(GrContext* context);
+ void unpinImages(GrContext* context);
+
+ /**
+ * If a SkiaDisplayList is deleted on the UI thread we cache a list of any
+ * images that need unpinned from the GPU cache and call this function on
+ * a subsequent frame to perform that cleanup.
+ */
+ static void cleanupImages(GrContext* context);
+
+ /**
+ * We use std::deque here because (1) we need to iterate through these
+ * elements and (2) mDrawable holds pointers to the elements, so they cannot
+ * relocate.
+ */
+ std::deque<RenderNodeDrawable> mChildNodes;
+ std::deque<GLFunctorDrawable> mChildFunctors;
+ std::vector<SkImage*> mMutableImages;
+ std::vector<VectorDrawableRoot*> mVectorDrawables;
+ sk_sp<SkLiteDL> mDrawable;
+
+ bool mIsProjectionReceiver = false;
+
+private:
+ bool mPinnedImages = false;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaDrawables.h b/libs/hwui/SkiaDrawables.h
new file mode 100644
index 0000000..a1ceeaa
--- /dev/null
+++ b/libs/hwui/SkiaDrawables.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Layer.h"
+#include "RenderNode.h"
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+#include <SkMatrix.h>
+
+#include <utils/RefBase.h>
+#include <utils/FatVector.h>
+#include <utils/Functor.h>
+
+namespace android {
+
+class Functor;
+
+namespace uirenderer {
+
+
+class RenderProperties;
+class OffscreenBuffer;
+class GlFunctorLifecycleListener;
+class SkiaDisplayList;
+
+/**
+ * This drawable wraps a RenderNode and enables it to be recorded into a list
+ * of Skia drawing commands.
+ */
+class RenderNodeDrawable : public SkDrawable {
+public:
+ explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas)
+ : mRenderNode(node)
+ , mRecordedTransform(canvas->getTotalMatrix()) {}
+
+ /**
+ * The renderNode (and its properties) that is to be drawn
+ */
+ RenderNode* getRenderNode() const { return mRenderNode.get(); }
+
+ /**
+ * Returns the transform on the canvas at time of recording and is used for
+ * computing total transform without rerunning DL contents.
+ */
+ const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
+
+protected:
+ virtual SkRect onGetBounds() override {
+ // We don't want to enable a record time quick reject because the properties
+ // of the RenderNode may be updated on subsequent frames.
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override { /* TODO */ }
+
+private:
+ sp<RenderNode> mRenderNode;
+ const SkMatrix mRecordedTransform;
+};
+
+/**
+ * This drawable wraps a OpenGL functor enabling it to be recorded into a list
+ * of Skia drawing commands.
+ */
+class GLFunctorDrawable : public SkDrawable {
+public:
+ GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
+ : mFunctor(functor)
+ , mListener(listener) {
+ canvas->getClipBounds(&mBounds);
+ }
+ virtual ~GLFunctorDrawable() {}
+
+ void syncFunctor() const { (*mFunctor)(DrawGlInfo::kModeSync, nullptr); }
+
+ protected:
+ virtual SkRect onGetBounds() override { return mBounds; }
+ virtual void onDraw(SkCanvas* canvas) override { /* TODO */ }
+
+ private:
+ Functor* mFunctor;
+ sp<GlFunctorLifecycleListener> mListener;
+ SkRect mBounds;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 6f4a683..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>
@@ -173,15 +174,16 @@
outData->gradientSampler = 0;
outData->gradientTexture = nullptr;
- outData->startColor.set(gradInfo.fColors[0]);
- outData->endColor.set(gradInfo.fColors[1]);
+ outData->startColor.setSRGB(gradInfo.fColors[0]);
+ outData->endColor.setSRGB(gradInfo.fColors[1]);
}
- outData->ditherSampler = (*textureUnit)++;
return true;
}
-void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data) {
+void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data,
+ const GLsizei width, const GLsizei height) {
+
if (CC_UNLIKELY(data.gradientTexture)) {
caches.textureState().activateTexture(data.gradientSampler);
bindTexture(&caches, data.gradientTexture, data.wrapST, data.wrapST);
@@ -191,10 +193,7 @@
bindUniformColor(caches.program().getUniform("endColor"), data.endColor);
}
- // TODO: remove sampler slot incrementing from dither.setupProgram,
- // since this assignment of slots is done at store, not apply time
- GLuint ditherSampler = data.ditherSampler;
- caches.dither.setupProgram(caches.program(), &ditherSampler);
+ glUniform2f(caches.program().getUniform("screenSize"), 1.0f / width, 1.0f / height);
glUniformMatrix4fv(caches.program().getUniform("screenSpace"), 1,
GL_FALSE, &data.screenSpace.data[0]);
}
@@ -208,13 +207,9 @@
return false;
}
- /*
- * Bypass the AssetAtlas, since those textures:
- * 1) require UV mapping, which isn't implemented in matrix computation below
- * 2) can't handle REPEAT simply
- * 3) are safe to upload here (outside of sync stage), since they're static
- */
- outData->bitmapTexture = caches.textureCache.getAndBypassAtlas(&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)++;
@@ -388,11 +383,12 @@
outData->skiaShaderType = kNone_SkiaShaderType;
}
-void SkiaShader::apply(Caches& caches, const SkiaShaderData& data) {
+void SkiaShader::apply(Caches& caches, const SkiaShaderData& data,
+ const GLsizei width, const GLsizei height) {
if (!data.skiaShaderType) return;
if (data.skiaShaderType & kGradient_SkiaShaderType) {
- applyGradient(caches, data.gradientData);
+ applyGradient(caches, data.gradientData, width, height);
}
if (data.skiaShaderType & kBitmap_SkiaShaderType) {
applyBitmap(caches, data.bitmapData);
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 884196d..5854289 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -62,7 +62,6 @@
} bitmapData;
struct GradientShaderData {
Matrix4 screenSpace;
- GLuint ditherSampler;
// simple gradient
FloatColor startColor;
@@ -72,7 +71,6 @@
Texture* gradientTexture;
GLuint gradientSampler;
GLenum wrapST;
-
} gradientData;
struct LayerShaderData {
Layer* layer;
@@ -90,7 +88,8 @@
static void store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix,
GLuint* textureUnit, ProgramDescription* description,
SkiaShaderData* outData);
- static void apply(Caches& caches, const SkiaShaderData& data);
+ static void apply(Caches& caches, const SkiaShaderData& data,
+ const GLsizei width, const GLsizei height);
};
}; // namespace uirenderer
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index e2ee5bf..7b0a1bc 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -927,9 +927,13 @@
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], newPenumbra[i].x,
newPenumbra[i].y, PENUMBRA_ALPHA);
}
+ // Since the umbra can be a faked one when the occluder is too high, the umbra should be lighter
+ // in this case.
+ float scaledUmbraAlpha = UMBRA_ALPHA * shadowStrengthScale;
+
for (int i = 0; i < umbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], umbra[i].x, umbra[i].y,
- UMBRA_ALPHA);
+ scaledUmbraAlpha);
}
for (int i = 0; i < verticesPairIndex; i++) {
@@ -969,14 +973,14 @@
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++],
- closerVertex.x, closerVertex.y, UMBRA_ALPHA);
+ closerVertex.x, closerVertex.y, scaledUmbraAlpha);
}
} else {
// If there is no occluded umbra at all, then draw the triangle fan
// starting from the centroid to all umbra vertices.
int lastCentroidIndex = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid.x,
- centroid.y, UMBRA_ALPHA);
+ centroid.y, scaledUmbraAlpha);
for (int i = 0; i < umbraLength; i++) {
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = lastCentroidIndex;
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 4f49a35..908f572 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -26,19 +26,23 @@
namespace android {
namespace uirenderer {
+// Number of bytes used by a texture in the given format
static int bytesPerPixel(GLint glFormat) {
switch (glFormat) {
// The wrapped-texture case, usually means a SurfaceTexture
case 0:
return 0;
+ case GL_LUMINANCE:
case GL_ALPHA:
return 1;
+ case GL_SRGB8:
case GL_RGB:
return 3;
+ case GL_SRGB8_ALPHA8:
case GL_RGBA:
return 4;
case GL_RGBA16F:
- return 16;
+ return 8;
default:
LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat);
}
@@ -83,14 +87,16 @@
mId = 0;
}
-bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) {
- if (mWidth == width && mHeight == height && mFormat == format) {
+bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
+ if (mWidth == width && mHeight == height &&
+ mFormat == format && mInternalFormat == internalFormat) {
return false;
}
mWidth = width;
mHeight = height;
mFormat = format;
- notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat));
+ mInternalFormat = internalFormat;
+ notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat));
return true;
}
@@ -101,10 +107,10 @@
mMagFilter = GL_LINEAR;
}
-void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
+void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height,
GLenum format, GLenum type, const void* pixels) {
GL_CHECKPOINT(MODERATE);
- bool needsAlloc = updateSize(width, height, internalformat);
+ bool needsAlloc = updateSize(width, height, internalFormat, format);
if (!mId) {
glGenTextures(1, &mId);
needsAlloc = true;
@@ -112,17 +118,17 @@
}
mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
if (needsAlloc) {
- glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0,
format, type, pixels);
} else if (pixels) {
- glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0,
format, type, pixels);
}
GL_CHECKPOINT(MODERATE);
}
-static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
- GLsizei width, GLsizei height, const GLvoid * data) {
+static void uploadToTexture(bool resize, GLint internalFormat, GLenum format, GLenum type,
+ GLsizei stride, GLsizei bpp, GLsizei width, GLsizei height, const GLvoid * data) {
const bool useStride = stride != width
&& Caches::getInstance().extensions().hasUnpackRowLength();
@@ -132,7 +138,7 @@
}
if (resize) {
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
}
@@ -156,7 +162,7 @@
}
if (resize) {
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, temp);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
}
@@ -166,31 +172,44 @@
}
static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
- bool resize, GLenum format, GLenum type) {
- uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
- bitmap.width(), bitmap.height(), bitmap.getPixels());
+ bool resize, GLint internalFormat, GLenum format, GLenum type) {
+ uploadToTexture(resize, internalFormat, format, type, bitmap.rowBytesAsPixels(),
+ bitmap.bytesPerPixel(), bitmap.width(), bitmap.height(), bitmap.getPixels());
}
-static void colorTypeToGlFormatAndType(SkColorType colorType,
- GLint* outFormat, GLint* outType) {
+static void colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType,
+ bool needSRGB, GLint* outInternalFormat, GLint* outFormat, GLint* outType) {
switch (colorType) {
case kAlpha_8_SkColorType:
*outFormat = GL_ALPHA;
+ *outInternalFormat = GL_ALPHA;
*outType = GL_UNSIGNED_BYTE;
break;
case kRGB_565_SkColorType:
- *outFormat = GL_RGB;
- *outType = GL_UNSIGNED_SHORT_5_6_5;
+ if (needSRGB) {
+ // We would ideally use a GL_RGB/GL_SRGB8 texture but the
+ // intermediate Skia bitmap needs to be ARGB_8888
+ *outFormat = GL_RGBA;
+ *outInternalFormat = caches.rgbaInternalFormat();
+ *outType = GL_UNSIGNED_BYTE;
+ } else {
+ *outFormat = GL_RGB;
+ *outInternalFormat = GL_RGB;
+ *outType = GL_UNSIGNED_SHORT_5_6_5;
+ }
break;
// ARGB_4444 and Index_8 are both upconverted to RGBA_8888
case kARGB_4444_SkColorType:
case kIndex_8_SkColorType:
case kN32_SkColorType:
*outFormat = GL_RGBA;
+ *outInternalFormat = caches.rgbaInternalFormat(needSRGB);
*outType = GL_UNSIGNED_BYTE;
break;
case kGray_8_SkColorType:
+ // TODO: Handle sRGB
*outFormat = GL_LUMINANCE;
+ *outInternalFormat = GL_LUMINANCE;
*outType = GL_UNSIGNED_BYTE;
break;
default:
@@ -224,29 +243,36 @@
setDefaultParams = true;
}
- GLint format, type;
- colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type);
+ sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+ bool needSRGB = bitmap.colorSpace() == sRGB.get();
- if (updateSize(bitmap.width(), bitmap.height(), format)) {
+ GLint internalFormat, format, type;
+ colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type);
+
+ if (updateSize(bitmap.width(), bitmap.height(), internalFormat, format)) {
needsAlloc = true;
}
blend = !bitmap.isOpaque();
mCaches.textureState().bindTexture(mId);
+ // TODO: Handle sRGB gray bitmaps
+ bool hasSRGB = mCaches.extensions().hasSRGB();
if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType
- || bitmap.colorType() == kIndex_8_SkColorType)) {
+ || bitmap.colorType() == kIndex_8_SkColorType
+ || (bitmap.colorType() == kRGB_565_SkColorType && hasSRGB && needSRGB))) {
+
SkBitmap rgbaBitmap;
- rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight,
- bitmap.alphaType()));
+ rgbaBitmap.allocPixels(SkImageInfo::MakeN32(
+ mWidth, mHeight, bitmap.alphaType(), hasSRGB ? sRGB : nullptr));
rgbaBitmap.eraseColor(0);
SkCanvas canvas(rgbaBitmap);
canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
- uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type);
+ uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, internalFormat, format, type);
} else {
- uploadSkBitmapToTexture(bitmap, needsAlloc, format, type);
+ uploadSkBitmapToTexture(bitmap, needsAlloc, internalFormat, format, type);
}
if (canMipMap) {
@@ -262,11 +288,12 @@
}
}
-void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) {
+void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
mId = id;
mWidth = width;
mHeight = height;
mFormat = format;
+ mInternalFormat = internalFormat;
// We're wrapping an existing texture, so don't double count this memory
notifySizeChanged(0);
}
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index b72742f..aa8a6d3 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -69,8 +69,8 @@
*
* The image data is undefined after calling this.
*/
- void resize(uint32_t width, uint32_t height, GLint format) {
- upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+ void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
+ upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr);
}
/**
@@ -85,13 +85,13 @@
/**
* Basically glTexImage2D/glTexSubImage2D.
*/
- void upload(GLint internalformat, uint32_t width, uint32_t height,
+ void upload(GLint internalFormat, uint32_t width, uint32_t height,
GLenum format, GLenum type, const void* pixels);
/**
* Wraps an existing texture.
*/
- void wrap(GLuint id, uint32_t width, uint32_t height, GLint format);
+ void wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format);
GLuint id() const {
return mId;
@@ -109,6 +109,10 @@
return mFormat;
}
+ GLint internalFormat() const {
+ return mInternalFormat;
+ }
+
/**
* Generation of the backing bitmap,
*/
@@ -148,13 +152,14 @@
friend class Layer;
// Returns true if the size changed, false if it was the same
- bool updateSize(uint32_t width, uint32_t height, GLint format);
+ bool updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format);
void resetCachedParams();
GLuint mId = 0;
uint32_t mWidth = 0;
uint32_t mHeight = 0;
GLint mFormat = 0;
+ GLint mInternalFormat = 0;
/* See GLES spec section 3.8.14
* "In the initial state, the value assigned to TEXTURE_MIN_FILTER is
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 523924a..08641b7 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -18,12 +18,12 @@
#include <utils/Mutex.h>
-#include "AssetAtlas.h"
#include "Caches.h"
#include "Texture.h"
#include "TextureCache.h"
#include "Properties.h"
#include "utils/TraceUtils.h"
+#include "hwui/Bitmap.h"
namespace android {
namespace uirenderer {
@@ -36,8 +36,7 @@
: mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity)
, mSize(0)
, mMaxSize(Properties::textureCacheSize)
- , mFlushRate(Properties::textureCacheFlushRate)
- , mAssetAtlas(nullptr) {
+ , mFlushRate(Properties::textureCacheFlushRate) {
mCache.setOnEntryRemovedListener(this);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
@@ -84,10 +83,6 @@
// Caching
///////////////////////////////////////////////////////////////////////////////
-void TextureCache::setAssetAtlas(AssetAtlas* assetAtlas) {
- mAssetAtlas = assetAtlas;
-}
-
void TextureCache::resetMarkInUse(void* ownerToken) {
LruCache<uint32_t, Texture*>::Iterator iter(mCache);
while (iter.next()) {
@@ -97,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);
@@ -108,15 +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, AtlasUsageType atlasUsageType) {
- if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
- AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef());
- if (CC_UNLIKELY(entry)) {
- return entry->texture;
- }
- }
-
- Texture* texture = mCache.get(bitmap->pixelRef()->getStableID());
+Texture* TextureCache::getCachedTexture(Bitmap* bitmap) {
+ Texture* texture = mCache.get(bitmap->getStableID());
if (!texture) {
if (!canMakeTextureFromBitmap(bitmap)) {
@@ -139,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",
@@ -147,32 +137,34 @@
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) {
- Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use);
+bool TextureCache::prefetchAndMarkInUse(void* ownerToken, Bitmap* bitmap) {
+ Texture* texture = getCachedTexture(bitmap);
if (texture) {
texture->isInUse = ownerToken;
}
return texture;
}
-bool TextureCache::prefetch(const SkBitmap* bitmap) {
- return getCachedTexture(bitmap, AtlasUsageType::Use);
+bool TextureCache::prefetch(Bitmap* bitmap) {
+ return getCachedTexture(bitmap);
}
-Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
- Texture* texture = getCachedTexture(bitmap, atlasUsageType);
+Texture* TextureCache::get(Bitmap* bitmap) {
+ Texture* texture = getCachedTexture(bitmap);
if (!texture) {
if (!canMakeTextureFromBitmap(bitmap)) {
@@ -182,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 0a61b6b..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;
@@ -47,8 +50,6 @@
// Classes
///////////////////////////////////////////////////////////////////////////////
-class AssetAtlas;
-
/**
* A simple LRU texture cache. The cache has a maximum size expressed in bytes.
* Any texture added to the cache causing the cache to grow beyond the maximum
@@ -75,30 +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 either within the cache, or
- * the AssetAtlas. If the texture cannot be found in the cache, a new texture is generated.
+ * 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) {
- return get(bitmap, AtlasUsageType::Use);
- }
-
- /**
- * Returns the texture associated with the specified bitmap. If the texture cannot be found in
- * the cache, a new texture is generated, even if it resides in the AssetAtlas.
- */
- Texture* getAndBypassAtlas(const SkBitmap* bitmap) {
- return get(bitmap, AtlasUsageType::Bypass);
- }
+ Texture* get(Bitmap* bitmap);
/**
* Removes the texture associated with the specified pixelRef. This is meant
@@ -130,18 +121,10 @@
*/
void flush();
- void setAssetAtlas(AssetAtlas* assetAtlas);
-
private:
- enum class AtlasUsageType {
- Use,
- Bypass,
- };
+ bool canMakeTextureFromBitmap(Bitmap* bitmap);
- bool canMakeTextureFromBitmap(const SkBitmap* bitmap);
-
- Texture* get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType);
- Texture* getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType);
+ Texture* getCachedTexture(Bitmap* bitmap);
LruCache<uint32_t, Texture*> mCache;
@@ -155,8 +138,6 @@
std::vector<uint32_t> mGarbage;
mutable Mutex mLock;
-
- AssetAtlas* mAssetAtlas;
}; // class TextureCache
}; // namespace uirenderer
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 2b79941..b50647a 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -201,8 +201,7 @@
SkPaint paint;
if (properties.getFillGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
- SkShader* newShader = properties.getFillGradient()->newWithLocalMatrix(matrix);
- paint.setShader(newShader);
+ paint.setShader(properties.getFillGradient()->makeWithLocalMatrix(matrix));
needsFill = true;
} else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -221,8 +220,7 @@
bool needsStroke = false;
if (properties.getStrokeGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
- SkShader* newShader = properties.getStrokeGradient()->newWithLocalMatrix(matrix);
- paint.setShader(newShader);
+ paint.setShader(properties.getStrokeGradient()->makeWithLocalMatrix(matrix));
needsStroke = true;
} else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
@@ -504,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);
}
@@ -530,49 +528,53 @@
if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) {
return nullptr;
} else {
- outPaint->setColorFilter(prop->getColorFilter());
+ outPaint->setColorFilter(sk_ref_sp(prop->getColorFilter()));
outPaint->setFilterQuality(kLow_SkFilterQuality);
outPaint->setAlpha(prop->getRootAlpha() * 255);
return outPaint;
}
}
-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)) {
- SkImageInfo info = SkImageInfo::Make(width, height,
- kN32_SkColorType, kPremul_SkAlphaType);
- outCache->setInfo(info);
- // TODO: Count the bitmap cache against app's java heap
- outCache->allocPixels(info);
+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);
+ 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/Vertex.h b/libs/hwui/Vertex.h
index c1bf980..db982ad 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -19,6 +19,7 @@
#include "Vector.h"
+#include "FloatColor.h"
#include "utils/Macros.h"
namespace android {
@@ -76,21 +77,19 @@
REQUIRE_COMPATIBLE_LAYOUT(TextureVertex);
/**
- * Simple structure to describe a vertex with a position, texture UV and ARGB color.
+ * Simple structure to describe a vertex with a position, texture UV and an
+ * sRGB color with alpha. The color is stored pre-multiplied in linear space.
*/
struct ColorTextureVertex {
float x, y;
float u, v;
- float r, g, b, a;
+ float r, g, b, a; // pre-multiplied linear
static inline void set(ColorTextureVertex* vertex, float x, float y,
- float u, float v, int color) {
-
- float a = ((color >> 24) & 0xff) / 255.0f;
- float r = a * ((color >> 16) & 0xff) / 255.0f;
- float g = a * ((color >> 8) & 0xff) / 255.0f;
- float b = a * ((color) & 0xff) / 255.0f;
- *vertex = { x, y, u, v, r, g, b, a };
+ float u, float v, uint32_t color) {
+ FloatColor c;
+ c.set(color);
+ *vertex = { x, y, u, v, c.r, c.g, c.b, c.a };
}
}; // struct ColorTextureVertex
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 49e9f65..e2844ad06 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -180,7 +180,12 @@
mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
}
- mTexture.resize(mWidth, mHeight, mFormat);
+ GLint internalFormat = mFormat;
+ if (mFormat == GL_RGBA) {
+ internalFormat = mCaches.rgbaInternalFormat();
+ }
+
+ mTexture.resize(mWidth, mHeight, internalFormat, mFormat);
mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
mTexture.setWrap(GL_CLAMP_TO_EDGE);
}
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/Bitmap.h b/libs/hwui/hwui/Bitmap.h
new file mode 100644
index 0000000..e86ac11
--- /dev/null
+++ b/libs/hwui/hwui/Bitmap.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <SkBitmap.h>
+#include <SkColorTable.h>
+#include <SkImageInfo.h>
+#include <SkPixelRef.h>
+#include <cutils/compiler.h>
+
+namespace android {
+
+enum class PixelStorageType {
+ External,
+ Heap,
+ Ashmem,
+};
+
+typedef void (*FreeFunc)(void* addr, void* context);
+
+class ANDROID_API Bitmap : public SkPixelRef {
+public:
+ 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);
+ Bitmap(void* address, void* context, FreeFunc freeFunc,
+ const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
+ Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
+ size_t rowBytes, SkColorTable* ctable);
+
+ int width() const { return info().width(); }
+ int height() const { return info().height(); }
+
+ // 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 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);
+ void reconfigure(const SkImageInfo& info);
+ void setAlphaType(SkAlphaType alphaType);
+
+ void getSkBitmap(SkBitmap* outBitmap);
+
+ 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:
+ virtual ~Bitmap();
+ void doFreePixels();
+ void* getStorage() const;
+
+ PixelStorageType mPixelStorageType;
+
+ size_t mRowBytes = 0;
+ sk_sp<SkColorTable> mColorTable;
+ bool mHasHardwareMipMap = false;
+
+ union {
+ struct {
+ void* address;
+ void* context;
+ FreeFunc freeFunc;
+ } external;
+ struct {
+ void* address;
+ int fd;
+ size_t size;
+ } ashmem;
+ struct {
+ void* address;
+ size_t size;
+ } heap;
+ } mPixelStorage;
+};
+
+} //namespace android
\ No newline at end of file
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 d76143b..d7839b4 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -21,7 +21,7 @@
#include "GlFunctorLifecycleListener.h"
#include "utils/Macros.h"
-#include "utils/NinePatch.h"
+#include <androidfw/ResourceTypes.h>
#include <SkBitmap.h>
#include <SkCanvas.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;
@@ -174,7 +193,7 @@
// ----------------------------------------------------------------------------
// Canvas draw operations
// ----------------------------------------------------------------------------
- virtual void drawColor(int color, SkXfermode::Mode mode) = 0;
+ virtual void drawColor(int color, SkBlendMode mode) = 0;
virtual void drawPaint(const SkPaint& paint) = 0;
// Geometry
@@ -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/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 18c35b3..33ee108 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -22,6 +22,8 @@
#include "Typeface.h"
+#include <pthread.h>
+
#include "MinikinSkia.h"
#include "SkTypeface.h"
#include "SkPaint.h"
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index 93f787d..8865c6e 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -25,70 +25,70 @@
* Structure mapping Skia xfermodes to OpenGL blending factors.
*/
struct Blender {
- SkXfermode::Mode mode;
+ SkBlendMode mode;
GLenum src;
GLenum dst;
};
// assumptions made by lookup tables in either this file or ProgramCache
-static_assert(0 == SkXfermode::kClear_Mode, "SkXfermode enums have changed");
-static_assert(1 == SkXfermode::kSrc_Mode, "SkXfermode enums have changed");
-static_assert(2 == SkXfermode::kDst_Mode, "SkXfermode enums have changed");
-static_assert(3 == SkXfermode::kSrcOver_Mode, "SkXfermode enums have changed");
-static_assert(4 == SkXfermode::kDstOver_Mode, "SkXfermode enums have changed");
-static_assert(5 == SkXfermode::kSrcIn_Mode, "SkXfermode enums have changed");
-static_assert(6 == SkXfermode::kDstIn_Mode, "SkXfermode enums have changed");
-static_assert(7 == SkXfermode::kSrcOut_Mode, "SkXfermode enums have changed");
-static_assert(8 == SkXfermode::kDstOut_Mode, "SkXfermode enums have changed");
-static_assert(9 == SkXfermode::kSrcATop_Mode, "SkXfermode enums have changed");
-static_assert(10 == SkXfermode::kDstATop_Mode, "SkXfermode enums have changed");
-static_assert(11 == SkXfermode::kXor_Mode, "SkXfermode enums have changed");
-static_assert(12 == SkXfermode::kPlus_Mode, "SkXfermode enums have changed");
-static_assert(13 == SkXfermode::kModulate_Mode, "SkXfermode enums have changed");
-static_assert(14 == SkXfermode::kScreen_Mode, "SkXfermode enums have changed");
-static_assert(15 == SkXfermode::kOverlay_Mode, "SkXfermode enums have changed");
-static_assert(16 == SkXfermode::kDarken_Mode, "SkXfermode enums have changed");
-static_assert(17 == SkXfermode::kLighten_Mode, "SkXfermode enums have changed");
+static_assert(0 == static_cast<int>(SkBlendMode::kClear), "SkBlendMode enums have changed");
+static_assert(1 == static_cast<int>(SkBlendMode::kSrc), "SkBlendMode enums have changed");
+static_assert(2 == static_cast<int>(SkBlendMode::kDst), "SkBlendMode enums have changed");
+static_assert(3 == static_cast<int>(SkBlendMode::kSrcOver), "SkBlendMode enums have changed");
+static_assert(4 == static_cast<int>(SkBlendMode::kDstOver), "SkBlendMode enums have changed");
+static_assert(5 == static_cast<int>(SkBlendMode::kSrcIn), "SkBlendMode enums have changed");
+static_assert(6 == static_cast<int>(SkBlendMode::kDstIn), "SkBlendMode enums have changed");
+static_assert(7 == static_cast<int>(SkBlendMode::kSrcOut), "SkBlendMode enums have changed");
+static_assert(8 == static_cast<int>(SkBlendMode::kDstOut), "SkBlendMode enums have changed");
+static_assert(9 == static_cast<int>(SkBlendMode::kSrcATop), "SkBlendMode enums have changed");
+static_assert(10 == static_cast<int>(SkBlendMode::kDstATop), "SkBlendMode enums have changed");
+static_assert(11 == static_cast<int>(SkBlendMode::kXor), "SkBlendMode enums have changed");
+static_assert(12 == static_cast<int>(SkBlendMode::kPlus), "SkBlendMode enums have changed");
+static_assert(13 == static_cast<int>(SkBlendMode::kModulate), "SkBlendMode enums have changed");
+static_assert(14 == static_cast<int>(SkBlendMode::kScreen), "SkBlendMode enums have changed");
+static_assert(15 == static_cast<int>(SkBlendMode::kOverlay), "SkBlendMode enums have changed");
+static_assert(16 == static_cast<int>(SkBlendMode::kDarken), "SkBlendMode enums have changed");
+static_assert(17 == static_cast<int>(SkBlendMode::kLighten), "SkBlendMode enums have changed");
// In this array, the index of each Blender equals the value of the first
-// entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode]
+// entry. For instance, gBlends[1] == gBlends[SkBlendMode::kSrc]
const Blender kBlends[] = {
- { SkXfermode::kClear_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kSrc_Mode, GL_ONE, GL_ZERO },
- { SkXfermode::kDst_Mode, GL_ZERO, GL_ONE },
- { SkXfermode::kSrcOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kDstOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE },
- { SkXfermode::kSrcIn_Mode, GL_DST_ALPHA, GL_ZERO },
- { SkXfermode::kDstIn_Mode, GL_ZERO, GL_SRC_ALPHA },
- { SkXfermode::kSrcOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
- { SkXfermode::kDstOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kSrcATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kDstATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
- { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kPlus_Mode, GL_ONE, GL_ONE },
- { SkXfermode::kModulate_Mode, GL_ZERO, GL_SRC_COLOR },
- { SkXfermode::kScreen_Mode, GL_ONE, GL_ONE_MINUS_SRC_COLOR }
+ { SkBlendMode::kClear, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kSrc, GL_ONE, GL_ZERO },
+ { SkBlendMode::kDst, GL_ZERO, GL_ONE },
+ { SkBlendMode::kSrcOver, GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kDstOver, GL_ONE_MINUS_DST_ALPHA, GL_ONE },
+ { SkBlendMode::kSrcIn, GL_DST_ALPHA, GL_ZERO },
+ { SkBlendMode::kDstIn, GL_ZERO, GL_SRC_ALPHA },
+ { SkBlendMode::kSrcOut, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
+ { SkBlendMode::kDstOut, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kSrcATop, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kDstATop, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
+ { SkBlendMode::kXor, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kPlus, GL_ONE, GL_ONE },
+ { SkBlendMode::kModulate, GL_ZERO, GL_SRC_COLOR },
+ { SkBlendMode::kScreen, GL_ONE, GL_ONE_MINUS_SRC_COLOR }
};
-// This array contains the swapped version of each SkXfermode. For instance
+// This array contains the swapped version of each SkBlendMode. For instance
// this array's SrcOver blending mode is actually DstOver. You can refer to
// createLayer() for more information on the purpose of this array.
const Blender kBlendsSwap[] = {
- { SkXfermode::kClear_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
- { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE },
- { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO },
- { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE },
- { SkXfermode::kDstOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kSrcIn_Mode, GL_ZERO, GL_SRC_ALPHA },
- { SkXfermode::kDstIn_Mode, GL_DST_ALPHA, GL_ZERO },
- { SkXfermode::kSrcOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kDstOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
- { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
- { SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
- { SkXfermode::kPlus_Mode, GL_ONE, GL_ONE },
- { SkXfermode::kModulate_Mode, GL_DST_COLOR, GL_ZERO },
- { SkXfermode::kScreen_Mode, GL_ONE_MINUS_DST_COLOR, GL_ONE }
+ { SkBlendMode::kClear, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
+ { SkBlendMode::kSrc, GL_ZERO, GL_ONE },
+ { SkBlendMode::kDst, GL_ONE, GL_ZERO },
+ { SkBlendMode::kSrcOver, GL_ONE_MINUS_DST_ALPHA, GL_ONE },
+ { SkBlendMode::kDstOver, GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kSrcIn, GL_ZERO, GL_SRC_ALPHA },
+ { SkBlendMode::kDstIn, GL_DST_ALPHA, GL_ZERO },
+ { SkBlendMode::kSrcOut, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kDstOut, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
+ { SkBlendMode::kSrcATop, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
+ { SkBlendMode::kDstATop, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kXor, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
+ { SkBlendMode::kPlus, GL_ONE, GL_ONE },
+ { SkBlendMode::kModulate, GL_DST_COLOR, GL_ZERO },
+ { SkBlendMode::kScreen, GL_ONE_MINUS_DST_COLOR, GL_ONE }
};
Blend::Blend()
@@ -111,9 +111,10 @@
}
}
-void Blend::getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage, GLenum* outSrc, GLenum* outDst) {
- *outSrc = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[mode].src : kBlends[mode].src;
- *outDst = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[mode].dst : kBlends[mode].dst;
+void Blend::getFactors(SkBlendMode mode, ModeOrderSwap modeUsage, GLenum* outSrc, GLenum* outDst) {
+ int index = static_cast<int>(mode);
+ *outSrc = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[index].src : kBlends[index].src;
+ *outDst = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[index].dst : kBlends[index].dst;
}
void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index df9e5a8..ec0e114 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -20,7 +20,7 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include <SkXfermode.h>
+#include <SkBlendMode.h>
#include <memory>
namespace android {
@@ -36,7 +36,7 @@
};
void syncEnabled();
- static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage,
+ static void getFactors(SkBlendMode mode, ModeOrderSwap modeUsage,
GLenum* outSrc, GLenum* outDst);
void setFactors(GLenum src, GLenum dst);
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index 10a26e0..a9bbb27 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -22,6 +22,7 @@
#include "utils/FatVector.h"
#include "utils/TraceUtils.h"
+#include <utils/Color.h>
#include <utils/Log.h>
#include <GLES2/gl2.h>
@@ -44,7 +45,7 @@
uint32_t height = computeIdealDimension(viewportHeight);
ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height);
caches.textureState().activateTexture(0);
- texture.resize(width, height, GL_RGBA);
+ texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA);
texture.blend = true;
texture.setWrap(GL_CLAMP_TO_EDGE);
// not setting filter on texture, since it's set when drawing, based on transform
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index ee4619d..84ab3f3 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -52,7 +52,6 @@
mCaches = &Caches::createInstance(*this);
}
mCaches->init();
- mCaches->textureCache.setAssetAtlas(&mAssetAtlas);
}
static void layerLostGlContext(Layer* layer) {
@@ -64,7 +63,6 @@
// TODO: reset all cached state in state objects
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
- mAssetAtlas.terminate();
mCaches->terminate();
@@ -147,9 +145,17 @@
meshState().resetVertexPointers();
meshState().disableTexCoordsVertexArray();
debugOverdraw(false, false);
+ // TODO: We need a way to know whether the functor is sRGB aware (b/32072673)
+ if (mCaches->extensions().hasSRGBWriteControl()) {
+ glDisable(GL_FRAMEBUFFER_SRGB_EXT);
+ }
}
void RenderState::resumeFromFunctorInvoke() {
+ if (mCaches->extensions().hasSRGBWriteControl()) {
+ glEnable(GL_FRAMEBUFFER_SRGB_EXT);
+ }
+
glViewport(0, 0, mViewportWidth, mViewportHeight);
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
debugOverdraw(false, false);
@@ -308,7 +314,7 @@
glVertexAttribPointer(alphaLocation, 1, GL_FLOAT, GL_FALSE, vertices.stride, alphaCoords);
}
// Shader uniforms
- SkiaShader::apply(*mCaches, fill.skiaShaderData);
+ SkiaShader::apply(*mCaches, fill.skiaShaderData, mViewportWidth, mViewportHeight);
GL_CHECKPOINT(MODERATE);
Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ?
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 9e0fb12..3d119dc 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -16,7 +16,6 @@
#ifndef RENDERSTATE_H
#define RENDERSTATE_H
-#include "AssetAtlas.h"
#include "Caches.h"
#include "Glop.h"
#include "renderstate/Blend.h"
@@ -92,7 +91,6 @@
void render(const Glop& glop, const Matrix4& orthoMatrix);
- AssetAtlas& assetAtlas() { return mAssetAtlas; }
Blend& blend() { return *mBlend; }
MeshState& meshState() { return *mMeshState; }
Scissor& scissor() { return *mScissor; }
@@ -120,7 +118,6 @@
OffscreenBufferPool mLayerPool;
- AssetAtlas mAssetAtlas;
std::set<Layer*> mActiveLayers;
std::set<renderthread::CanvasContext*> mRegisteredContexts;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 43471e5..8ed8893 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -85,6 +85,43 @@
return nullptr;
}
+void CanvasContext::destroyLayer(RenderNode* node) {
+ auto renderType = Properties::getRenderPipelineType();
+ switch (renderType) {
+ case RenderPipelineType::OpenGL:
+ OpenGLPipeline::destroyLayer(node);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
+ }
+}
+
+void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) {
+ ATRACE_CALL();
+ auto renderType = Properties::getRenderPipelineType();
+ switch (renderType) {
+ case RenderPipelineType::OpenGL:
+ OpenGLPipeline::invokeFunctor(thread, functor);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
+ }
+}
+
+void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+ auto renderType = Properties::getRenderPipelineType();
+ switch (renderType) {
+ case RenderPipelineType::OpenGL:
+ OpenGLPipeline::prepareToDraw(thread, bitmap);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
+ }
+}
+
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory,
std::unique_ptr<IRenderPipeline> renderPipeline)
@@ -413,8 +450,11 @@
GpuMemoryTracker::onFrameCompleted();
#ifdef BUGREPORT_FONT_CACHE_USAGE
- Caches& caches = Caches::getInstance();
- caches.fontRenderer.getFontRenderer().historyTracker().frameCompleted();
+ auto renderType = Properties::getRenderPipelineType();
+ if (RenderPipelineType::OpenGL == renderType) {
+ Caches& caches = Caches::getInstance();
+ caches.fontRenderer.getFontRenderer().historyTracker().frameCompleted();
+ }
#endif
}
@@ -444,16 +484,6 @@
}
}
-void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
- ATRACE_CALL();
- DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
- if (thread.eglManager().hasEglContext()) {
- mode = DrawGlInfo::kModeProcess;
- }
-
- thread.renderState().invokeFunctor(functor, mode, nullptr);
-}
-
void CanvasContext::markLayerInUse(RenderNode* node) {
if (mPrefetchedLayers.erase(node)) {
node->decStrong(nullptr);
@@ -508,10 +538,6 @@
for (const sp<RenderNode>& node : mRenderNodes) {
node->destroyHardwareResources(observer);
}
- Caches& caches = Caches::getInstance();
- // Make sure to release all the textures we were owning as there won't
- // be another draw
- caches.textureCache.resetMarkInUse(this);
mRenderPipeline->onDestroyHardwareResources();
}
}
@@ -533,11 +559,6 @@
return mRenderPipeline->createTextureLayer();
}
-void CanvasContext::setTextureAtlas(RenderThread& thread,
- const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) {
- thread.eglManager().setTextureAtlas(buffer, map, mapSize);
-}
-
void CanvasContext::dumpFrames(int fd) {
FILE* file = fdopen(fd, "a");
fprintf(file, "\n\n---PROFILEDATA---\n");
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 7ebe0ae..b61eef2 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -68,6 +68,33 @@
RenderNode* rootRenderNode, IContextFactory* contextFactory);
virtual ~CanvasContext();
+ /**
+ * Update or create a layer specific for the provided RenderNode. The layer
+ * attached to the node will be specific to the RenderPipeline used by this
+ * context
+ *
+ * @return true if the layer has been created or updated
+ */
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator) {
+ return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator);
+ }
+
+ /**
+ * Destroy any layers that have been attached to the provided RenderNode removing
+ * any state that may have been set during createOrUpdateLayer().
+ */
+ static void destroyLayer(RenderNode* node);
+
+ static void invokeFunctor(const RenderThread& thread, Functor* functor);
+
+ static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap);
+
+ /*
+ * If Properties::isSkiaEnabled() is true then this will return the Skia
+ * grContext associated with the current RenderPipeline.
+ */
+ GrContext* getGrContext() const { return mRenderThread.getGrContext(); }
+
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
@@ -98,13 +125,8 @@
void destroyHardwareResources(TreeObserver* observer);
static void trimMemory(RenderThread& thread, int level);
- static void invokeFunctor(RenderThread& thread, Functor* functor);
-
DeferredLayerUpdater* createTextureLayer();
- ANDROID_API static void setTextureAtlas(RenderThread& thread,
- const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
-
void stopDrawing();
void notifyFramePending();
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 86731c9..ce48bc0 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -25,6 +25,8 @@
#include <cutils/log.h>
#include <cutils/properties.h>
#include <EGL/eglext.h>
+#include <GrContextOptions.h>
+#include <gl/GrGLInterface.h>
#include <string>
#define GLES_VERSION 2
@@ -91,9 +93,7 @@
, mEglConfig(nullptr)
, mEglContext(EGL_NO_CONTEXT)
, mPBufferSurface(EGL_NO_SURFACE)
- , mCurrentSurface(EGL_NO_SURFACE)
- , mAtlasMap(nullptr)
- , mAtlasMapSize(0) {
+ , mCurrentSurface(EGL_NO_SURFACE) {
}
void EglManager::initialize() {
@@ -128,7 +128,17 @@
makeCurrent(mPBufferSurface);
DeviceInfo::initialize();
mRenderThread.renderState().onGLContextCreated();
- initAtlas();
+
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
+ sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
+ LOG_ALWAYS_FATAL_IF(!glInterface.get());
+
+ GrContextOptions options;
+ options.fDisableDistanceFieldPaths = true;
+ options.fAllowPathMaskCaching = true;
+ mRenderThread.setGrContext(GrContext::Create(GrBackend::kOpenGL_GrBackend,
+ (GrBackendContext)glInterface.get(), options));
+ }
}
void EglManager::initExtensions() {
@@ -191,32 +201,6 @@
"Failed to create context, error = %s", egl_error_str());
}
-void EglManager::setTextureAtlas(const sp<GraphicBuffer>& buffer,
- int64_t* map, size_t mapSize) {
-
- // Already initialized
- if (mAtlasBuffer.get()) {
- ALOGW("Multiple calls to setTextureAtlas!");
- delete map;
- return;
- }
-
- mAtlasBuffer = buffer;
- mAtlasMap = map;
- mAtlasMapSize = mapSize;
-
- if (hasEglContext()) {
- initAtlas();
- }
-}
-
-void EglManager::initAtlas() {
- if (mAtlasBuffer.get()) {
- mRenderThread.renderState().assetAtlas().init(mAtlasBuffer,
- mAtlasMap, mAtlasMapSize);
- }
-}
-
void EglManager::createPBufferSurface() {
LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
"usePBufferSurface() called on uninitialized GlobalContext!");
@@ -229,7 +213,16 @@
EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
initialize();
- EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr);
+
+ EGLint attribs[] = {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
+ EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
+#endif
+ EGL_NONE
+ };
+
+ EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs);
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
"Failed to create EGLSurface for window %p, eglErr = %s",
(void*) window, egl_error_str());
@@ -255,6 +248,7 @@
void EglManager::destroy() {
if (mEglDisplay == EGL_NO_DISPLAY) return;
+ mRenderThread.setGrContext(nullptr);
mRenderThread.renderState().onGLContextDestroyed();
eglDestroyContext(mEglDisplay, mEglContext);
eglDestroySurface(mEglDisplay, mPBufferSurface);
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 41047fe..ba4a3e1 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -79,8 +79,6 @@
// Returns true iff the surface is now preserving buffers.
bool setPreserveBuffer(EGLSurface surface, bool preserve);
- void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
-
void fence();
private:
@@ -94,7 +92,6 @@
void createPBufferSurface();
void loadConfig();
void createContext();
- void initAtlas();
EGLint queryBufferAge(EGLSurface surface);
RenderThread& mRenderThread;
@@ -106,10 +103,6 @@
EGLSurface mCurrentSurface;
- sp<GraphicBuffer> mAtlasBuffer;
- int64_t* mAtlasMap;
- size_t mAtlasMapSize;
-
enum class SwapBehavior {
Discard,
Preserved,
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 3250fed..52894ad 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -22,6 +22,8 @@
#include <SkRect.h>
#include <utils/RefBase.h>
+class GrContext;
+
namespace android {
class Surface;
@@ -43,6 +45,8 @@
Succeeded
};
+class Frame;
+
class IRenderPipeline {
public:
virtual MakeCurrentResult makeCurrent() = 0;
@@ -67,6 +71,8 @@
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo) = 0;
virtual TaskManager* getTaskManager() = 0;
+ virtual bool createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator) = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index c8971f8..cca0fca 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -18,6 +18,7 @@
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
+#include "ProfileRenderer.h"
#include "renderstate/RenderState.h"
#include "Readback.h"
@@ -76,7 +77,8 @@
BakedOpRenderer renderer(caches, mRenderThread.renderState(),
opaque, lightInfo);
frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
- profiler->draw(&renderer);
+ ProfileRenderer profileRenderer(renderer);
+ profiler->draw(profileRenderer);
drew = renderer.didDraw();
// post frame cleanup
@@ -163,6 +165,10 @@
}
void OpenGLPipeline::onDestroyHardwareResources() {
+ Caches& caches = Caches::getInstance();
+ // Make sure to release all the textures we were owning as there won't
+ // be another draw
+ caches.textureCache.resetMarkInUse(this);
mRenderThread.renderState().flush(Caches::FlushMode::Layers);
}
@@ -183,6 +189,61 @@
return &Caches::getInstance().tasks;
}
+static bool layerMatchesWH(OffscreenBuffer* layer, int width, int height) {
+ return layer->viewportWidth == (uint32_t)width && layer->viewportHeight == (uint32_t)height;
+}
+
+bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator) {
+ RenderState& renderState = mRenderThread.renderState();
+ OffscreenBufferPool& layerPool = renderState.layerPool();
+ bool transformUpdateNeeded = false;
+ if (node->getLayer() == nullptr) {
+ node->setLayer(layerPool.get(renderState, node->getWidth(), node->getHeight()));
+ transformUpdateNeeded = true;
+ } else if (!layerMatchesWH(node->getLayer(), node->getWidth(), node->getHeight())) {
+ // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering)
+ // Or, ideally, maintain damage between frames on node/layer so ordering is always correct
+ if (node->properties().fitsOnLayer()) {
+ node->setLayer(layerPool.resize(node->getLayer(), node->getWidth(), node->getHeight()));
+ } else {
+ destroyLayer(node);
+ }
+ transformUpdateNeeded = true;
+ }
+
+ if (transformUpdateNeeded && node->getLayer()) {
+ // update the transform in window of the layer to reset its origin wrt light source position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getLayer()->setWindowTransform(windowTransform);
+ }
+
+ return transformUpdateNeeded;
+}
+
+void OpenGLPipeline::destroyLayer(RenderNode* node) {
+ if (OffscreenBuffer* layer = node->getLayer()) {
+ layer->renderState.layerPool().putOrDelete(layer);
+ node->setLayer(nullptr);
+ }
+}
+
+void OpenGLPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+ if (Caches::hasInstance() && thread.eglManager().hasEglContext()) {
+ ATRACE_NAME("Bitmap#prepareToDraw task");
+ Caches::getInstance().textureCache.prefetch(bitmap);
+ }
+}
+
+void OpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) {
+ DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
+ if (thread.eglManager().hasEglContext()) {
+ mode = DrawGlInfo::kModeProcess;
+ }
+ thread.renderState().invokeFunctor(functor, mode, nullptr);
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index e08fd9b..8722d59 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -26,9 +26,6 @@
namespace uirenderer {
namespace renderthread {
-class Frame;
-
-
class OpenGLPipeline : public IRenderPipeline {
public:
OpenGLPipeline(RenderThread& thread);
@@ -56,6 +53,11 @@
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo) override;
TaskManager* getTaskManager() override;
+ bool createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator) override;
+ static void destroyLayer(RenderNode* node);
+ static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap);
+ static void invokeFunctor(const RenderThread& thread, Functor* functor);
private:
EglManager& mEglManager;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index dcbc980..6cb2b9c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -480,23 +480,6 @@
staticPostAndWait(task);
}
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
- size_t size) {
- CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
- args->buffer->decStrong(nullptr);
- return nullptr;
-}
-
-void RenderProxy::setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size) {
- SETUP_TASK(setTextureAtlas);
- args->thread = &mRenderThread;
- args->buffer = buffer.get();
- args->buffer->incStrong(nullptr);
- args->map = map;
- args->size = size;
- post(task);
-}
-
CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
args->thread->jankTracker().switchStorageToAshmem(args->fd);
close(args->fd);
@@ -634,17 +617,14 @@
reinterpret_cast<intptr_t>( staticPostAndWait(task) ));
}
-CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, SkBitmap* bitmap) {
- if (Caches::hasInstance() && args->thread->eglManager().hasEglContext()) {
- ATRACE_NAME("Bitmap#prepareToDraw task");
- Caches::getInstance().textureCache.prefetch(args->bitmap);
- }
- delete args->bitmap;
+CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, Bitmap* bitmap) {
+ CanvasContext::prepareToDraw(*args->thread, 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
@@ -653,7 +633,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 d4aaea6..ae9330d 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -112,7 +112,6 @@
uint32_t frameTimePercentile(int p);
ANDROID_API static void dumpGraphicsMemory(int fd);
- ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
ANDROID_API void setProcessStatsBuffer(int fd);
ANDROID_API int getRenderThreadTid();
@@ -129,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/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 076e3d4..d8677e13 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -22,6 +22,7 @@
#include "../JankTracker.h"
#include "TimeLord.h"
+#include <GrContext.h>
#include <cutils/compiler.h>
#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
@@ -88,12 +89,15 @@
void pushBackFrameCallback(IFrameCallback* callback);
TimeLord& timeLord() { return mTimeLord; }
- RenderState& renderState() { return *mRenderState; }
- EglManager& eglManager() { return *mEglManager; }
+ RenderState& renderState() const { return *mRenderState; }
+ EglManager& eglManager() const { return *mEglManager; }
JankTracker& jankTracker() { return *mJankTracker; }
const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
+ GrContext* getGrContext() const { return mGrContext.get(); }
+ void setGrContext(GrContext* cxt) { mGrContext.reset(cxt); }
+
protected:
virtual bool threadLoop() override;
@@ -144,6 +148,8 @@
EglManager* mEglManager;
JankTracker* mJankTracker = nullptr;
+
+ sk_sp<GrContext> mGrContext;
};
} /* namespace renderthread */
diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp
index b8484b9..6d2e8599 100644
--- a/libs/hwui/tests/common/TestListViewSceneBase.cpp
+++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp
@@ -47,7 +47,7 @@
}
});
- canvas.drawColor(Color::Grey_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Grey_500, SkBlendMode::kSrcOver);
canvas.drawRenderNode(mListView.get());
}
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 78e9bc4..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,14 +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;
- SkImageInfo info = SkImageInfo::Make(width, height,
- colorType, kPremul_SkAlphaType);
- 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/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
index 47f40a1..8f2ba2d 100644
--- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
@@ -29,7 +29,7 @@
public:
sp<RenderNode> card;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
card = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, Canvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
@@ -39,7 +39,7 @@
canvas.rotate(45);
canvas.translate(-100, -100);
canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
- canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Blue_500, SkBlendMode::kSrcOver);
}
canvas.restore();
@@ -48,7 +48,7 @@
SkPath clipCircle;
clipCircle.addCircle(100, 300, 100);
canvas.clipPath(&clipCircle, SkRegion::kIntersect_Op);
- canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
}
canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index 9d6aa53..c0d9450 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -37,7 +37,7 @@
container = TestUtils::createNode(0, 0, width, height, nullptr);
doFrame(0); // update container
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
canvas.drawRenderNode(container.get());
}
diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
index 00dba78..3a230ae 100644
--- a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
@@ -32,9 +32,9 @@
card = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, Canvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
- canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFF0000FF, SkBlendMode::kSrcOver);
});
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver); // background
canvas.drawRenderNode(card.get());
}
void doFrame(int frameNr) override {
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index a5e91e4..b7357e1 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -31,7 +31,7 @@
class ListOfFadedTextAnimation : public TestListViewSceneBase {
void createListItem(RenderProperties& props, Canvas& canvas, int id,
int itemWidth, int itemHeight) override {
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
int length = dp(100);
canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer);
SkPaint textPaint;
@@ -44,16 +44,15 @@
pts[1].set(0, 1);
SkColor colors[2] = {Color::Black, Color::Transparent};
- SkAutoTUnref<SkShader> s(SkGradientShader::CreateLinear(pts, colors, NULL, 2,
+ sk_sp<SkShader> s(SkGradientShader::MakeLinear(pts, colors, NULL, 2,
SkShader::kClamp_TileMode));
SkMatrix matrix;
matrix.setScale(1, length);
matrix.postRotate(-90);
SkPaint fadingPaint;
- fadingPaint.setShader(s->newWithLocalMatrix(matrix))->unref();
- SkXfermode* mode = SkXfermode::Create(SkXfermode::kDstOut_Mode);
- fadingPaint.setXfermode(mode);
+ fadingPaint.setShader(s->makeWithLocalMatrix(matrix));
+ fadingPaint.setBlendMode(SkBlendMode::kDstOut);
canvas.drawRect(0, 0, length, itemHeight, fadingPaint);
canvas.restore();
}
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/OpPropAnimation.cpp b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
index c8e124c..68051d6 100644
--- a/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
@@ -53,7 +53,7 @@
mCircleX->value = width * 0.75;
mCircleY->value = height * 0.75;
- canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
canvas.drawRoundRect(mRoundRectLeft.get(), mRoundRectTop.get(),
mRoundRectRight.get(), mRoundRectBottom.get(),
mRoundRectRx.get(), mRoundRectRy.get(), mPaint.get());
diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
index f37c00c..d6fd604 100644
--- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
@@ -29,7 +29,7 @@
public:
sp<RenderNode> card;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
card = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, Canvas& canvas) {
SkPaint paint;
diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
index bc2dc75..bc04d81 100644
--- a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
@@ -37,7 +37,7 @@
0xFF4CAF50,
};
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
@@ -45,7 +45,7 @@
sp<RenderNode> card = TestUtils::createNode(x, y,
x + dp(100), y + dp(100),
[color](RenderProperties& props, Canvas& canvas) {
- canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(color, SkBlendMode::kSrcOver);
});
canvas.drawRenderNode(card.get());
cards.push_back(card);
@@ -61,7 +61,7 @@
TestUtils::recordNode(*cards[0], [curFrame](Canvas& canvas) {
SkColor color = TestUtils::interpolateColor(
curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0);
- canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(color, SkBlendMode::kSrcOver);
});
}
};
diff --git a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
index 3d4397a..8256024 100644
--- a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
@@ -39,18 +39,20 @@
thumbnailSize = std::min(std::min(width, height) / 2, 720);
int cardsize = std::min(width, height) - dp(64);
- renderer.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ renderer.drawColor(Color::White, SkBlendMode::kSrcOver);
renderer.insertReorderBarrier(true);
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,15 +70,14 @@
}
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));
props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
props.mutableOutline().setShouldClip(true);
- canvas.drawColor(Color::Grey_200, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Grey_200, SkBlendMode::kSrcOver);
canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(),
0, 0, width, height, nullptr);
});
diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
index e1d323e..668eec6 100644
--- a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
@@ -30,12 +30,12 @@
public:
sp<RenderNode> card;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(50, 50, 250, 250,
[](RenderProperties& props, Canvas& canvas) {
- canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFF00FF, SkBlendMode::kSrcOver);
SkRegion region;
for (int xOffset = 0; xOffset < 200; xOffset+=2) {
diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
index 0f8906e..4b6632d 100644
--- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
@@ -28,7 +28,7 @@
std::vector< sp<RenderNode> > cards;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
canvas.insertReorderBarrier(true);
int ci = 0;
@@ -37,7 +37,7 @@
auto color = BrightColors[ci++ % BrightColorsCount];
auto card = TestUtils::createNode(x, y, x + mSize, y + mSize,
[&](RenderProperties& props, Canvas& canvas) {
- canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(color, SkBlendMode::kSrcOver);
props.mutableOutline().setRoundRect(0, 0,
props.getWidth(), props.getHeight(), mSize * .25, 1);
props.mutableOutline().setShouldClip(true);
diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
index cd00ed3..3630935 100644
--- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
@@ -29,16 +29,16 @@
public:
sp<RenderNode> card;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); // background
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver); // background
card = TestUtils::createNode(0, 0, 400, 800,
[](RenderProperties& props, Canvas& canvas) {
// nested clipped saveLayers
canvas.saveLayerAlpha(0, 0, 400, 400, 200, SaveFlags::ClipToLayer);
- canvas.drawColor(Color::Green_700, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Green_700, SkBlendMode::kSrcOver);
canvas.clipRect(50, 50, 350, 350, SkRegion::kIntersect_Op);
canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
- canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Blue_500, SkBlendMode::kSrcOver);
canvas.restore();
canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
index d7d0c512..0a69b62 100644
--- a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
@@ -29,7 +29,7 @@
public:
std::vector< sp<RenderNode> > cards;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
canvas.insertReorderBarrier(true);
for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
@@ -57,7 +57,7 @@
props.setElevation(dp(16));
props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
props.mutableOutline().setShouldClip(true);
- canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFEEEEEE, SkBlendMode::kSrcOver);
});
}
};
diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
index 75362dd..4a02429 100644
--- a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
@@ -29,7 +29,7 @@
public:
std::vector< sp<RenderNode> > cards;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
canvas.insertReorderBarrier(true);
for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
@@ -57,7 +57,7 @@
props.setElevation(dp(16));
props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
props.mutableOutline().setShouldClip(true);
- canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFEEEEEE, SkBlendMode::kSrcOver);
});
}
};
diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
index e2370f7..5ef8773 100644
--- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
@@ -83,7 +83,7 @@
for (auto op : ops) {
int innerCount = canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(0, 0, cellSize, cellSize, SkRegion::kIntersect_Op);
- canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
op(canvas, cellSize, paint);
canvas.restoreToCount(innerCount);
canvas.translate(cellSize + cellSpace, 0);
@@ -94,7 +94,7 @@
}
canvas.restoreToCount(outerCount);
});
- canvas.drawColor(Color::Grey_500, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(Color::Grey_500, SkBlendMode::kSrcOver);
canvas.drawRenderNode(card.get());
}
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index 2933402..438f877 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -29,7 +29,7 @@
public:
sp<RenderNode> card;
void createContent(int width, int height, Canvas& canvas) override {
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
card = TestUtils::createNode(0, 0, width, height,
[](RenderProperties& props, Canvas& canvas) {
SkPaint paint;
diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
index 2787fba..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());
@@ -163,7 +163,7 @@
void BM_DisplayListCanvas_basicViewGroupDraw(benchmark::State& benchState) {
sp<RenderNode> child = TestUtils::createNode(50, 50, 100, 100,
[](auto& props, auto& canvas) {
- canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
});
std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(100, 100));
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/scripts/prep_ryu.sh b/libs/hwui/tests/scripts/prep_ryu.sh
new file mode 100644
index 0000000..7c6c0df
--- /dev/null
+++ b/libs/hwui/tests/scripts/prep_ryu.sh
@@ -0,0 +1,31 @@
+adb root
+adb wait-for-device
+adb shell stop thermal-engine
+adb shell stop perfd
+
+# 51000 102000 204000 306000 408000 510000 612000 714000 816000 918000
+# 1020000 1122000 1224000 1326000 1428000 1530000 1632000 1734000 1836000 1912500
+S=1326000
+echo "set cpu to $S hz";
+adb shell "echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"
+
+#01: core 76 MHz emc 408 MHz
+#02: core 153 MHz emc 665 MHz
+#03: core 230 MHz emc 800 MHz *
+#04: core 307 MHz emc 1065 MHz
+#05: core 384 MHz emc 1331 MHz
+#06: core 460 MHz emc 1600 MHz
+#07: core 537 MHz emc 1600 MHz
+#08: core 614 MHz emc 1600 MHz
+#09: core 691 MHz emc 1600 MHz
+#0a: core 768 MHz emc 1600 MHz
+#0b: core 844 MHz emc 1600 MHz
+#0c: core 921 MHz emc 1600 MHz
+#0d: core 998 MHz emc 1600 MHz
+#AC: core 230 MHz emc 800 MHz a A d D
+
+echo "set gpu to core 307 MHz emc 1065 MHz"
+# it will lock gpu until you touch a screen
+adb shell "echo 04 > /sys/devices/57000000.gpu/pstate"
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index 9deb441..d44be7d 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -86,9 +86,7 @@
strokePaint.setStrokeWidth(4);
float intervals[] = {1.0f, 1.0f};
- auto dashEffect = SkDashPathEffect::Create(intervals, 2, 0);
- strokePaint.setPathEffect(dashEffect);
- dashEffect->unref();
+ strokePaint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
auto textureGlopVerifier = [] (const Glop& glop) {
// validate glop produced by renderPathTexture (so texture, unit quad)
@@ -167,7 +165,7 @@
shadowPaint.setColor(SK_ColorRED);
SkScalar sigma = Blur::convertRadiusToSigma(5);
- shadowPaint.setLooper(SkBlurDrawLooper::Create(SK_ColorWHITE, sigma, 3, 3))->unref();
+ shadowPaint.setLooper(SkBlurDrawLooper::Make(SK_ColorWHITE, sigma, 3, 3));
TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25);
TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50);
@@ -202,8 +200,8 @@
props.mutateLayerProperties().setType(LayerType::RenderLayer);
// provide different blend mode, so decoration draws contrast
- props.mutateLayerProperties().setXferMode(SkXfermode::Mode::kSrc_Mode);
- canvas.drawColor(Color::Black, SkXfermode::Mode::kSrcOver_Mode);
+ props.mutateLayerProperties().setXferMode(SkBlendMode::kSrc);
+ canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
});
OffscreenBuffer** layerHandle = node->getLayerHandle();
@@ -287,4 +285,4 @@
EXPECT_EQ(1, reinterpret_cast<PathTexture*>(texture)->left);
EXPECT_EQ(3, reinterpret_cast<PathTexture*>(texture)->top);
});
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
new file mode 100644
index 0000000..3cc7189
--- /dev/null
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "AnimationContext.h"
+#include "IContextFactory.h"
+#include "renderthread/CanvasContext.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+class ContextFactory : public IContextFactory {
+public:
+ virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+ return new AnimationContext(clock);
+ }
+};
+
+RENDERTHREAD_TEST(CanvasContext, create) {
+ auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+ renderThread, false, rootNode.get(), &contextFactory));
+
+ ASSERT_FALSE(canvasContext->hasSurface());
+
+ canvasContext->destroy(nullptr);
+}
+
+class TestFunctor : public Functor {
+public:
+ bool didProcess = false;
+
+ virtual status_t operator ()(int what, void* data) {
+ if (what == DrawGlInfo::kModeProcess) { didProcess = true; }
+ return DrawGlInfo::kStatusDone;
+ }
+};
+
+RENDERTHREAD_TEST(CanvasContext, invokeFunctor) {
+ TestFunctor functor;
+ ASSERT_FALSE(functor.didProcess);
+ CanvasContext::invokeFunctor(renderThread, &functor);
+ ASSERT_TRUE(functor.didProcess);
+}
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 259686b..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,
@@ -477,6 +478,35 @@
EXPECT_EQ(4, renderer.getIndex());
}
+RENDERTHREAD_TEST(FrameBuilder, regionClipStopsMerge) {
+ class RegionClipStopsMergeTestRenderer : public TestRendererBase {
+ public:
+ void onTextOp(const TextOp& op, const BakedOpState& state) override { mIndex++; }
+ };
+ auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPath path;
+ path.addCircle(200, 200, 200, SkPath::kCW_Direction);
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipPath(&path, SkRegion::kIntersect_Op);
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 200);
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ RegionClipStopsMergeTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
RENDERTHREAD_TEST(FrameBuilder, textMerging) {
class TextMergingTestRenderer : public TestRendererBase {
public:
@@ -721,7 +751,7 @@
auto unclippedColorView = TestUtils::createNode<RecordingCanvas>(0, 0, 10, 10,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.setClipToBounds(false);
- canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
});
FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
@@ -793,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
@@ -974,7 +1004,7 @@
void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
EXPECT_EQ(1, mIndex++);
ASSERT_NE(nullptr, op.paint);
- ASSERT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint));
+ ASSERT_EQ(SkBlendMode::kClear, PaintUtils::getBlendModeDirect(op.paint));
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
EXPECT_EQ(2, mIndex++);
@@ -1108,7 +1138,7 @@
void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
EXPECT_EQ(1, mIndex++);
ASSERT_NE(nullptr, op.paint);
- EXPECT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint));
+ EXPECT_EQ(SkBlendMode::kClear, PaintUtils::getBlendModeDirect(op.paint));
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds)
<< "Expect dirty rect as clip";
ASSERT_NE(nullptr, state.computedState.clipState);
@@ -1433,7 +1463,7 @@
auto node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
- canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
});
OffscreenBuffer** layerHandle = node->getLayerHandle();
@@ -2196,7 +2226,7 @@
auto node = TestUtils::createNode<RecordingCanvas>(20, 20, 30, 30,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.clipRect(0, -20, 10, 30, SkRegion::kReplace_Op);
- canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
});
FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 40, 40), 50, 50,
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index edc7191..134497c 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -249,7 +249,7 @@
TEST(RecordingCanvas, drawColor) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.drawColor(Color::Black, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
});
ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
@@ -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();
@@ -639,7 +638,7 @@
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(-10, -10, 110, 110, SkRegion::kReplace_Op);
- canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
canvas.restore();
});
ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
@@ -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,
@@ -773,7 +776,7 @@
SkShader::TileMode::kRepeat_TileMode);
sk_sp<SkShader> composeShader = SkShader::MakeComposeShader(std::move(shader1), std::move(shader2),
- SkXfermode::Mode::kMultiply_Mode);
+ SkXfermode::kMultiply_Mode);
paint.setShader(std::move(composeShader));
canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
});
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 0d90afa..331a6ac 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -41,7 +41,7 @@
TEST(RenderNode, hasParents) {
auto child = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, Canvas& canvas) {
- canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
});
auto parent = TestUtils::createNode(0, 0, 200, 400,
[&child](RenderProperties& props, Canvas& canvas) {
@@ -54,7 +54,7 @@
EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
TestUtils::recordNode(*parent, [](Canvas& canvas) {
- canvas.drawColor(Color::Amber_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Amber_500, SkBlendMode::kSrcOver);
});
EXPECT_TRUE(child->hasParents()) << "Child should still have a parent";
@@ -117,7 +117,7 @@
{
auto nonNullDLNode = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, Canvas& canvas) {
- canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
});
TestUtils::syncHierarchyPropertiesAndDisplayList(nonNullDLNode);
EXPECT_TRUE(nonNullDLNode->getDisplayList());
@@ -160,7 +160,7 @@
// Check that the VD is in the dislay list, and the layer update queue contains the correct
// damage rect.
- EXPECT_FALSE(rootNode->getDisplayList()->getVectorDrawables().empty());
+ EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables());
EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode);
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index a30ada0..49c4da6e 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -18,18 +18,27 @@
#include <gtest/gtest.h>
#include <SkColorMatrixFilter.h>
+#include <SkColorSpace.h>
#include <SkImagePriv.h>
#include <SkShader.h>
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,
@@ -48,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());
@@ -56,8 +65,8 @@
TEST(SkiaBehavior, lightingColorFilter_simplify) {
{
- SkAutoTUnref<SkColorFilter> filter(
- SkColorMatrixFilter::CreateLightingFilter(0x11223344, 0));
+ sk_sp<SkColorFilter> filter(
+ SkColorMatrixFilter::MakeLightingFilter(0x11223344, 0));
SkColor observedColor;
SkXfermode::Mode observedMode;
@@ -67,18 +76,24 @@
}
{
- SkAutoTUnref<SkColorFilter> failFilter(
- SkColorMatrixFilter::CreateLightingFilter(0x11223344, 0x1));
+ sk_sp<SkColorFilter> failFilter(
+ SkColorMatrixFilter::MakeLightingFilter(0x11223344, 0x1));
EXPECT_FALSE(failFilter->asColorMode(nullptr, nullptr));
}
}
TEST(SkiaBehavior, porterDuffCreateIsCached) {
SkPaint paint;
- paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
- auto expected = paint.getXfermode();
- paint.setXfermodeMode(SkXfermode::kClear_Mode);
- ASSERT_NE(expected, paint.getXfermode());
- paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
- ASSERT_EQ(expected, paint.getXfermode());
+ paint.setBlendMode(SkBlendMode::kOverlay);
+ auto expected = paint.getBlendMode();
+ paint.setBlendMode(SkBlendMode::kClear);
+ ASSERT_NE(expected, paint.getBlendMode());
+ paint.setBlendMode(SkBlendMode::kOverlay);
+ ASSERT_EQ(expected, paint.getBlendMode());
+}
+
+TEST(SkiaBehavior, srgbColorSpaceIsSingleton) {
+ sk_sp<SkColorSpace> sRGB1 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+ sk_sp<SkColorSpace> sRGB2 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+ ASSERT_EQ(sRGB1.get(), sRGB2.get());
}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 5a01193..451596d 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -45,7 +45,7 @@
SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0);
std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas));
TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25);
- SkAutoTUnref<const SkPicture> picture(recorder.endRecording());
+ sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
canvas.asSkCanvas()->drawPicture(picture);
});
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
new file mode 100644
index 0000000..7f622eb
--- /dev/null
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <VectorDrawable.h>
+
+#include "AnimationContext.h"
+#include "DamageAccumulator.h"
+#include "IContextFactory.h"
+#include "SkiaDisplayList.h"
+#include "renderthread/CanvasContext.h"
+#include "tests/common/TestUtils.h"
+
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+TEST(SkiaDisplayList, create) {
+ SkRect bounds = SkRect::MakeWH(200, 200);
+ SkiaDisplayList skiaDL(bounds);
+ ASSERT_TRUE(skiaDL.isEmpty());
+ ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+ ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
+}
+
+TEST(SkiaDisplayList, reset) {
+ SkRect bounds = SkRect::MakeWH(200, 200);
+ SkiaDisplayList skiaDL(bounds);
+
+ SkCanvas dummyCanvas;
+ skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas);
+ skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas);
+ skiaDL.mMutableImages.push_back(nullptr);
+ skiaDL.mVectorDrawables.push_back(nullptr);
+ skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr);
+ skiaDL.mIsProjectionReceiver = true;
+
+ ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
+ ASSERT_FALSE(skiaDL.mChildNodes.empty());
+ ASSERT_FALSE(skiaDL.mChildFunctors.empty());
+ ASSERT_FALSE(skiaDL.mMutableImages.empty());
+ ASSERT_FALSE(skiaDL.mVectorDrawables.empty());
+ ASSERT_FALSE(skiaDL.isEmpty());
+ ASSERT_TRUE(skiaDL.mIsProjectionReceiver);
+
+ bounds = SkRect::MakeWH(100, 100);
+ skiaDL.reset(nullptr, bounds);
+
+ ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
+ ASSERT_TRUE(skiaDL.mChildNodes.empty());
+ ASSERT_TRUE(skiaDL.mChildFunctors.empty());
+ ASSERT_TRUE(skiaDL.mMutableImages.empty());
+ ASSERT_TRUE(skiaDL.mVectorDrawables.empty());
+ ASSERT_TRUE(skiaDL.isEmpty());
+ ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+}
+
+TEST(SkiaDisplayList, reuseDisplayList) {
+ sp<RenderNode> renderNode = new RenderNode();
+ std::unique_ptr<SkiaDisplayList> availableList;
+
+ // no list has been attached so it should return a nullptr
+ availableList = renderNode->detachAvailableList();
+ ASSERT_EQ(availableList.get(), nullptr);
+
+ // attach a displayList for reuse
+ SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200));
+ ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get(), nullptr));
+
+ // detach the list that you just attempted to reuse
+ availableList = renderNode->detachAvailableList();
+ ASSERT_EQ(availableList.get(), &skiaDL);
+ availableList.release(); // prevents an invalid free since our DL is stack allocated
+
+ // after detaching there should return no available list
+ availableList = renderNode->detachAvailableList();
+ ASSERT_EQ(availableList.get(), nullptr);
+}
+
+class TestFunctor : public Functor {
+public:
+ bool didSync = false;
+
+ virtual status_t operator ()(int what, void* data) {
+ if (what == DrawGlInfo::kModeSync) { didSync = true; }
+ return DrawGlInfo::kStatusDone;
+ }
+};
+
+TEST(SkiaDisplayList, syncContexts) {
+ SkRect bounds = SkRect::MakeWH(200, 200);
+ SkiaDisplayList skiaDL(bounds);
+
+ SkCanvas dummyCanvas;
+ TestFunctor functor;
+ skiaDL.mChildFunctors.emplace_back(&functor, nullptr, &dummyCanvas);
+
+ VectorDrawableRoot vectorDrawable(new VectorDrawable::Group());
+ vectorDrawable.mutateStagingProperties()->setBounds(bounds);
+ skiaDL.mVectorDrawables.push_back(&vectorDrawable);
+
+ // ensure that the functor and vectorDrawable are properly synced
+ skiaDL.syncContents();
+
+ ASSERT_TRUE(functor.didSync);
+ ASSERT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds);
+}
+
+class ContextFactory : public IContextFactory {
+public:
+ virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+ return new AnimationContext(clock);
+ }
+};
+
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
+ auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+ renderThread, false, rootNode.get(), &contextFactory));
+ TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
+ DamageAccumulator damageAccumulator;
+ info.damageAccumulator = &damageAccumulator;
+ info.observer = nullptr;
+
+ SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200));
+
+ // prepare with a clean VD
+ VectorDrawableRoot cleanVD(new VectorDrawable::Group());
+ skiaDL.mVectorDrawables.push_back(&cleanVD);
+ cleanVD.getBitmapUpdateIfDirty(); // this clears the dirty bit
+
+ ASSERT_FALSE(cleanVD.isDirty());
+ ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
+ ASSERT_FALSE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {}));
+ ASSERT_TRUE(cleanVD.getPropertyChangeWillBeConsumed());
+
+ // prepare again this time adding a dirty VD
+ VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
+ skiaDL.mVectorDrawables.push_back(&dirtyVD);
+
+ ASSERT_TRUE(dirtyVD.isDirty());
+ ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
+ ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {}));
+ ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
+
+ // prepare again this time adding a RenderNode and a callback
+ sp<RenderNode> renderNode = new RenderNode();
+ TreeInfo* infoPtr = &info;
+ SkCanvas dummyCanvas;
+ skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
+ bool hasRun = false;
+ ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false,
+ [&hasRun, renderNode, infoPtr](RenderNode* n, TreeInfo& i, bool r) {
+ hasRun = true;
+ ASSERT_EQ(renderNode.get(), n);
+ ASSERT_EQ(infoPtr, &i);
+ ASSERT_FALSE(r);
+ }));
+ ASSERT_TRUE(hasRun);
+
+ canvasContext->destroy(nullptr);
+}
+
+TEST(SkiaDisplayList, updateChildren) {
+ SkRect bounds = SkRect::MakeWH(200, 200);
+ SkiaDisplayList skiaDL(bounds);
+
+ sp<RenderNode> renderNode = new RenderNode();
+ SkCanvas dummyCanvas;
+ skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
+ skiaDL.updateChildren([renderNode](RenderNode* n) {
+ ASSERT_EQ(renderNode.get(), n);
+ });
+}
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 83b485f..8e0d3ee 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -426,5 +426,49 @@
EXPECT_EQ(1.0f, properties->getPivotY());
}
+
+static SkShader* createShader(bool* isDestroyed) {
+ class TestShader : public SkShader {
+ public:
+ TestShader(bool* isDestroyed) : SkShader(), mDestroyed(isDestroyed) {
+ }
+ ~TestShader() {
+ *mDestroyed = true;
+ }
+
+ Factory getFactory() const override { return nullptr; }
+ private:
+ bool* mDestroyed;
+ };
+ return new TestShader(isDestroyed);
+}
+
+TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) {
+ VectorDrawable::FullPath path("m1 1", 4);
+ SkBitmap bitmap;
+ SkImageInfo info = SkImageInfo::Make(5, 5, kN32_SkColorType, kPremul_SkAlphaType);
+ bitmap.setInfo(info);
+ bitmap.allocPixels(info);
+ SkCanvas canvas(bitmap);
+
+ bool shaderIsDestroyed = false;
+
+ // Initial ref count is 1
+ SkShader* shader = createShader(&shaderIsDestroyed);
+
+ // Setting the fill gradient increments the ref count of the shader by 1
+ path.mutateStagingProperties()->setFillGradient(shader);
+ path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true);
+ // Resetting the fill gradient decrements the ref count of the shader by 1
+ path.mutateStagingProperties()->setFillGradient(nullptr);
+
+ // Expect ref count to be 1 again, i.e. nothing else to have a ref to the shader now. Unref()
+ // again should bring the ref count to zero and consequently trigger detor.
+ shader->unref();
+
+ // Verify that detor is called.
+ EXPECT_TRUE(shaderIsDestroyed);
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index b5157f4..f9cc46d 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -16,6 +16,8 @@
#ifndef COLOR_H
#define COLOR_H
+#include <math.h>
+
#include <SkColor.h>
namespace android {
@@ -80,6 +82,42 @@
};
static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color);
+ // Opto-electronic conversion function for the sRGB color space
+ // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
+ static constexpr float OECF_sRGB(float linear) {
+ // IEC 61966-2-1:1999
+ return linear <= 0.0031308f ?
+ linear * 12.92f : (powf(linear, 1.0f / 2.4f) * 1.055f) - 0.055f;
+ }
+
+ // Opto-electronic conversion function for the sRGB color space
+ // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
+ // This function returns the input unmodified if linear blending is not enabled
+ static constexpr float OECF(float linear) {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ return OECF_sRGB(linear);
+#else
+ return linear;
+#endif
+ }
+
+ // Electro-optical conversion function for the sRGB color space
+ // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
+ static constexpr float EOCF_sRGB(float srgb) {
+ // IEC 61966-2-1:1999
+ return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f);
+ }
+
+ // Electro-optical conversion function for the sRGB color space
+ // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
+ // This function returns the input unmodified if linear blending is not enabled
+ static constexpr float EOCF(float srgb) {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ return EOCF_sRGB(srgb);
+#else
+ return srgb;
+#endif
+ }
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/utils/NinePatch.h b/libs/hwui/utils/NinePatch.h
deleted file mode 100644
index 323e563..0000000
--- a/libs/hwui/utils/NinePatch.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-**
-** Copyright 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.
-*/
-
-#ifndef ANDROID_GRAPHICS_NINEPATCH_H
-#define ANDROID_GRAPHICS_NINEPATCH_H
-
-#include <androidfw/ResourceTypes.h>
-#include <cutils/compiler.h>
-
-#include "SkCanvas.h"
-#include "SkRegion.h"
-
-namespace android {
-
-class ANDROID_API NinePatch {
-public:
- static void Draw(SkCanvas* canvas, const SkRect& bounds, const SkBitmap& bitmap,
- const Res_png_9patch& chunk, const SkPaint* paint, SkRegion** outRegion);
-};
-
-} // namespace android
-
-#endif // ANDROID_GRAPHICS_NINEPATCH_H
diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp
deleted file mode 100644
index d37126c..0000000
--- a/libs/hwui/utils/NinePatchImpl.cpp
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
-**
-** Copyright 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.
-*/
-
-#include "utils/NinePatch.h"
-
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkColorPriv.h"
-#include "SkPaint.h"
-#include "SkUnPreMultiply.h"
-
-#include <utils/Log.h>
-
-namespace android {
-
-static const bool kUseTrace = true;
-static bool gTrace = false;
-
-static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
- switch (bitmap.colorType()) {
- case kN32_SkColorType:
- *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
- break;
- case kRGB_565_SkColorType:
- *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
- break;
- case kARGB_4444_SkColorType:
- *c = SkUnPreMultiply::PMColorToColor(
- SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
- break;
- case kIndex_8_SkColorType: {
- SkColorTable* ctable = bitmap.getColorTable();
- *c = SkUnPreMultiply::PMColorToColor(
- (*ctable)[*bitmap.getAddr8(x, y)]);
- break;
- }
- default:
- return false;
- }
- return true;
-}
-
-static SkColor modAlpha(SkColor c, int alpha) {
- int scale = alpha + (alpha >> 7);
- int a = SkColorGetA(c) * scale >> 8;
- return SkColorSetA(c, a);
-}
-
-static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
- const SkBitmap& bitmap, const SkPaint& paint,
- SkColor initColor, uint32_t colorHint,
- bool hasXfer) {
- if (colorHint != android::Res_png_9patch::NO_COLOR) {
- ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
- canvas->drawRect(dst, paint);
- ((SkPaint*)&paint)->setColor(initColor);
- } else if (src.width() == 1 && src.height() == 1) {
- SkColor c;
- if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
- goto SLOW_CASE;
- }
- if (0 != c || hasXfer) {
- SkColor prev = paint.getColor();
- ((SkPaint*)&paint)->setColor(c);
- canvas->drawRect(dst, paint);
- ((SkPaint*)&paint)->setColor(prev);
- }
- } else {
- SLOW_CASE:
- canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint);
- }
-}
-
-SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
- int srcSpace, int numStrechyPixelsRemaining,
- int numFixedPixelsRemaining) {
- SkScalar spaceRemaining = boundsLimit - startingPoint;
- SkScalar stretchySpaceRemaining =
- spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
- return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining;
-}
-
-void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds,
- const SkBitmap& bitmap, const Res_png_9patch& chunk,
- const SkPaint* paint, SkRegion** outRegion) {
- if (canvas && canvas->quickReject(bounds)) {
- return;
- }
-
- SkPaint defaultPaint;
- if (NULL == paint) {
- // matches default dither in NinePatchDrawable.java.
- defaultPaint.setDither(true);
- paint = &defaultPaint;
- }
-
- const int32_t* xDivs = chunk.getXDivs();
- const int32_t* yDivs = chunk.getYDivs();
-
- if (kUseTrace) {
- gTrace = true;
- }
-
- SkASSERT(canvas || outRegion);
-
- if (kUseTrace) {
- if (canvas) {
- const SkMatrix& m = canvas->getTotalMatrix();
- ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
- SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
- SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
- }
-
- ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()),
- SkScalarToFloat(bounds.height()));
- ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
- ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]);
- ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]);
- }
-
- if (bounds.isEmpty() ||
- bitmap.width() == 0 || bitmap.height() == 0 ||
- (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
- {
- if (kUseTrace) {
- ALOGV("======== abort ninepatch draw\n");
- }
- return;
- }
-
- // should try a quick-reject test before calling lockPixels
-
- SkAutoLockPixels alp(bitmap);
- // after the lock, it is valid to check getPixels()
- if (bitmap.getPixels() == NULL)
- return;
-
- const bool hasXfer = paint->getXfermode() != NULL;
- SkRect dst;
- SkIRect src;
-
- const int32_t x0 = xDivs[0];
- const int32_t y0 = yDivs[0];
- const SkColor initColor = ((SkPaint*)paint)->getColor();
- const uint8_t numXDivs = chunk.numXDivs;
- const uint8_t numYDivs = chunk.numYDivs;
- int i;
- int j;
- int colorIndex = 0;
- uint32_t color;
- bool xIsStretchable;
- const bool initialXIsStretchable = (x0 == 0);
- bool yIsStretchable = (y0 == 0);
- const int bitmapWidth = bitmap.width();
- const int bitmapHeight = bitmap.height();
-
- // Number of bytes needed for dstRights array.
- // Need to cast numXDivs to a larger type to avoid overflow.
- const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar);
- SkScalar* dstRights = (SkScalar*) alloca(dstBytes);
- bool dstRightsHaveBeenCached = false;
-
- int numStretchyXPixelsRemaining = 0;
- for (i = 0; i < numXDivs; i += 2) {
- numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i];
- }
- int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
- int numStretchyYPixelsRemaining = 0;
- for (i = 0; i < numYDivs; i += 2) {
- numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i];
- }
- int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
-
- if (kUseTrace) {
- ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
- bitmap.width(), bitmap.height(),
- SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
- SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
- numXDivs, numYDivs);
- }
-
- src.fTop = 0;
- dst.fTop = bounds.fTop;
- // The first row always starts with the top being at y=0 and the bottom
- // being either yDivs[1] (if yDivs[0]=0) or 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 = yIsStretchable ? 1 : 0;
- j <= numYDivs && src.fTop < bitmapHeight;
- j++, yIsStretchable = !yIsStretchable) {
- src.fLeft = 0;
- dst.fLeft = bounds.fLeft;
- if (j == numYDivs) {
- src.fBottom = bitmapHeight;
- dst.fBottom = bounds.fBottom;
- } else {
- src.fBottom = yDivs[j];
- const int srcYSize = src.fBottom - src.fTop;
- if (yIsStretchable) {
- dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
- srcYSize,
- numStretchyYPixelsRemaining,
- numFixedYPixelsRemaining);
- numStretchyYPixelsRemaining -= srcYSize;
- } else {
- dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
- numFixedYPixelsRemaining -= srcYSize;
- }
- }
-
- xIsStretchable = initialXIsStretchable;
- // The initial xDiv and whether the first column is considered
- // stretchable or not depends on whether xDiv[0] was zero or not.
- const uint32_t* colors = chunk.getColors();
- for (i = xIsStretchable ? 1 : 0;
- i <= numXDivs && src.fLeft < bitmapWidth;
- i++, xIsStretchable = !xIsStretchable) {
- color = colors[colorIndex++];
- if (i == numXDivs) {
- src.fRight = bitmapWidth;
- dst.fRight = bounds.fRight;
- } else {
- src.fRight = xDivs[i];
- if (dstRightsHaveBeenCached) {
- dst.fRight = dstRights[i];
- } else {
- const int srcXSize = src.fRight - src.fLeft;
- if (xIsStretchable) {
- dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
- srcXSize,
- numStretchyXPixelsRemaining,
- numFixedXPixelsRemaining);
- numStretchyXPixelsRemaining -= srcXSize;
- } else {
- dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
- numFixedXPixelsRemaining -= srcXSize;
- }
- dstRights[i] = dst.fRight;
- }
- }
- // If this horizontal patch is too small to be displayed, leave
- // the destination left edge where it is and go on to the next patch
- // in the source.
- if (src.fLeft >= src.fRight) {
- src.fLeft = src.fRight;
- continue;
- }
- // Make sure that we actually have room to draw any bits
- if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
- goto nextDiv;
- }
- // If this patch is transparent, skip and don't draw.
- if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
- if (outRegion) {
- if (*outRegion == NULL) {
- *outRegion = new SkRegion();
- }
- SkIRect idst;
- dst.round(&idst);
- //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
- // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
- (*outRegion)->op(idst, SkRegion::kUnion_Op);
- }
- goto nextDiv;
- }
- if (canvas) {
- if (kUseTrace) {
- ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
- src.fLeft, src.fTop, src.width(), src.height(),
- SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
- SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
- if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
- ALOGV("--- skip patch\n");
- }
- }
- drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
- color, hasXfer);
- }
-
-nextDiv:
- src.fLeft = src.fRight;
- dst.fLeft = dst.fRight;
- }
- src.fTop = src.fBottom;
- dst.fTop = dst.fBottom;
- dstRightsHaveBeenCached = true;
- }
-}
-
-} // namespace android
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 4faab9a..710e063 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -33,18 +33,6 @@
class PaintUtils {
public:
- /**
- * Safely retrieves the mode from the specified xfermode. If the specified
- * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode.
- */
- static inline SkXfermode::Mode getXfermode(SkXfermode* mode) {
- SkXfermode::Mode resultMode;
- if (!SkXfermode::AsMode(mode, &resultMode)) {
- resultMode = SkXfermode::kSrcOver_Mode;
- }
- return resultMode;
- }
-
static inline GLenum getFilter(const SkPaint* paint) {
if (!paint || paint->getFilterQuality() != kNone_SkFilterQuality) {
return GL_LINEAR;
@@ -56,7 +44,7 @@
static inline bool paintWillNotDraw(const SkPaint& paint) {
return paint.getAlpha() == 0
&& !paint.getColorFilter()
- && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
+ && paint.getBlendMode() == SkBlendMode::kSrcOver;
}
// TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
@@ -64,7 +52,7 @@
return paint.getAlpha() == 0
&& paint.getLooper() == nullptr
&& !paint.getColorFilter()
- && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
+ && paint.getBlendMode() == SkBlendMode::kSrcOver;
}
static bool isOpaquePaint(const SkPaint* paint) {
@@ -77,9 +65,9 @@
}
// Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
- SkXfermode::Mode mode = getXfermode(paint->getXfermode());
- return mode == SkXfermode::Mode::kSrcOver_Mode
- || mode == SkXfermode::Mode::kSrc_Mode;
+ SkBlendMode mode = paint->getBlendMode();
+ return mode == SkBlendMode::kSrcOver
+ || mode == SkBlendMode::kSrc;
}
static bool isBlendedShader(const SkShader* shader) {
@@ -121,8 +109,8 @@
return getTextShadow(paint, nullptr);
}
- static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) {
- return paint ? getXfermode(paint->getXfermode()) : SkXfermode::kSrcOver_Mode;
+ static inline SkBlendMode getBlendModeDirect(const SkPaint* paint) {
+ return paint ? paint->getBlendMode() : SkBlendMode::kSrcOver;
}
static inline int getAlphaDirect(const SkPaint* paint) {
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index b879f78..624d207 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -110,9 +110,10 @@
}
bool capturePixels(SkBitmap* bmp) {
+ sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
SkImageInfo destinationConfig =
SkImageInfo::Make(mSize.width(), mSize.height(),
- kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType, colorSpace);
bmp->allocPixels(destinationConfig);
android_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
mSize.width() * mSize.height() * 4);
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 0bc832a..6941dba 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -216,7 +216,7 @@
SkCanvas surfaceCanvas(surfaceBitmap);
SkPaint paint;
- paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ paint.setBlendMode(SkBlendMode::kSrc);
surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
if (outBuffer.width > update.state.icon.bitmap.width()) {
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/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
index f90f1e2..fb91bbb 100644
--- a/media/java/android/media/AmrInputStream.java
+++ b/media/java/android/media/AmrInputStream.java
@@ -18,45 +18,69 @@
import java.io.InputStream;
import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import android.media.MediaCodec.BufferInfo;
+import android.util.Log;
/**
* AmrInputStream
* @hide
*/
-public final class AmrInputStream extends InputStream
-{
- static {
- System.loadLibrary("media_jni");
- }
-
+public final class AmrInputStream extends InputStream {
private final static String TAG = "AmrInputStream";
// frame is 20 msec at 8.000 khz
private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
-
+
+ MediaCodec mCodec;
+ BufferInfo mInfo;
+ boolean mSawOutputEOS;
+ boolean mSawInputEOS;
+
// pcm input stream
private InputStream mInputStream;
-
- // native handle
- private long mGae;
-
+
// result amr stream
private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
private int mBufIn = 0;
private int mBufOut = 0;
-
+
// helper for bytewise read()
private byte[] mOneByte = new byte[1];
-
+
/**
* Create a new AmrInputStream, which converts 16 bit PCM to AMR
* @param inputStream InputStream containing 16 bit PCM.
*/
public AmrInputStream(InputStream inputStream) {
mInputStream = inputStream;
- mGae = GsmAmrEncoderNew();
- GsmAmrEncoderInitialize(mGae);
+
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
+ format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
+ format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, 12200);
+
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ String name = mcl.findEncoderForFormat(format);
+ if (name != null) {
+ try {
+ mCodec = MediaCodec.createByCodecName(name);
+ mCodec.configure(format,
+ null /* surface */,
+ null /* crypto */,
+ MediaCodec.CONFIGURE_FLAG_ENCODE);
+ mCodec.start();
+ } catch (IOException e) {
+ if (mCodec != null) {
+ mCodec.release();
+ }
+ mCodec = null;
+ }
+ }
+ mInfo = new BufferInfo();
}
@Override
@@ -64,7 +88,7 @@
int rtn = read(mOneByte, 0, 1);
return rtn == 1 ? (0xff & mOneByte[0]) : -1;
}
-
+
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
@@ -72,67 +96,100 @@
@Override
public int read(byte[] b, int offset, int length) throws IOException {
- if (mGae == 0) throw new IllegalStateException("not open");
-
- // local buffer of amr encoded audio empty
- if (mBufOut >= mBufIn) {
- // reset the buffer
+ if (mCodec == null) {
+ throw new IllegalStateException("not open");
+ }
+
+ if (mBufOut >= mBufIn && !mSawOutputEOS) {
+ // no data left in buffer, refill it
mBufOut = 0;
mBufIn = 0;
-
- // fetch a 20 msec frame of pcm
- for (int i = 0; i < SAMPLES_PER_FRAME * 2; ) {
- int n = mInputStream.read(mBuf, i, SAMPLES_PER_FRAME * 2 - i);
- if (n == -1) return -1;
- i += n;
+
+ // first push as much data into the encoder as possible
+ while (!mSawInputEOS) {
+ int index = mCodec.dequeueInputBuffer(0);
+ if (index < 0) {
+ // no input buffer currently available
+ break;
+ } else {
+ int numRead;
+ for (numRead = 0; numRead < SAMPLES_PER_FRAME * 2; ) {
+ int n = mInputStream.read(mBuf, numRead, SAMPLES_PER_FRAME * 2 - numRead);
+ if (n == -1) {
+ mSawInputEOS = true;
+ break;
+ }
+ numRead += n;
+ }
+ ByteBuffer buf = mCodec.getInputBuffer(index);
+ buf.put(mBuf, 0, numRead);
+ mCodec.queueInputBuffer(index,
+ 0 /* offset */,
+ numRead,
+ 0 /* presentationTimeUs */,
+ mSawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0 /* flags */);
+ }
}
-
- // encode it
- mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
+
+ // now read encoded data from the encoder (blocking, since we just filled up the
+ // encoder's input with data it should be able to output at least one buffer)
+ while (true) {
+ int index = mCodec.dequeueOutputBuffer(mInfo, -1);
+ if (index >= 0) {
+ mBufIn = mInfo.size;
+ ByteBuffer out = mCodec.getOutputBuffer(index);
+ out.get(mBuf, 0 /* offset */, mBufIn /* length */);
+ mCodec.releaseOutputBuffer(index, false /* render */);
+ if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mSawOutputEOS = true;
+ }
+ break;
+ }
+ }
}
-
- // return encoded audio to user
- if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
- System.arraycopy(mBuf, mBufOut, b, offset, length);
- mBufOut += length;
-
- return length;
+
+ if (mBufOut < mBufIn) {
+ // there is data in the buffer
+ if (length > mBufIn - mBufOut) {
+ length = mBufIn - mBufOut;
+ }
+ System.arraycopy(mBuf, mBufOut, b, offset, length);
+ mBufOut += length;
+ return length;
+ }
+
+ if (mSawInputEOS && mSawOutputEOS) {
+ // no more data available in buffer, codec or input stream
+ return -1;
+ }
+
+ // caller should try again
+ return 0;
}
@Override
public void close() throws IOException {
try {
- if (mInputStream != null) mInputStream.close();
+ if (mInputStream != null) {
+ mInputStream.close();
+ }
} finally {
mInputStream = null;
try {
- if (mGae != 0) GsmAmrEncoderCleanup(mGae);
- } finally {
- try {
- if (mGae != 0) GsmAmrEncoderDelete(mGae);
- } finally {
- mGae = 0;
+ if (mCodec != null) {
+ mCodec.release();
}
+ } finally {
+ mCodec = null;
}
}
}
@Override
protected void finalize() throws Throwable {
- if (mGae != 0) {
- close();
- throw new IllegalStateException("someone forgot to close AmrInputStream");
+ if (mCodec != null) {
+ Log.w(TAG, "AmrInputStream wasn't closed");
+ mCodec.release();
}
}
-
- //
- // AudioRecord JNI interface
- //
- private static native long GsmAmrEncoderNew();
- private static native void GsmAmrEncoderInitialize(long gae);
- private static native int GsmAmrEncoderEncode(long gae,
- byte[] pcm, int pcmOffset, byte[] amr, int amrOffset) throws IOException;
- private static native void GsmAmrEncoderCleanup(long gae);
- private static native void GsmAmrEncoderDelete(long gae);
-
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 33c1c3f..f24bf09 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -312,29 +312,32 @@
*/
public static final String EXTRA_ENCODINGS = "android.media.extra.ENCODINGS";
- /** The audio stream for phone calls */
+ /** Used to identify the volume of audio streams for phone calls */
public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
- /** The audio stream for system sounds */
+ /** Used to identify the volume of audio streams for system sounds */
public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM;
- /** The audio stream for the phone ring */
+ /** Used to identify the volume of audio streams for the phone ring */
public static final int STREAM_RING = AudioSystem.STREAM_RING;
- /** The audio stream for music playback */
+ /** Used to identify the volume of audio streams for music playback */
public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;
- /** The audio stream for alarms */
+ /** Used to identify the volume of audio streams for alarms */
public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;
- /** The audio stream for notifications */
+ /** Used to identify the volume of audio streams for notifications */
public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
- /** @hide The audio stream for phone calls when connected to bluetooth */
+ /** @hide Used to identify the volume of audio streams for phone calls when connected
+ * to bluetooth */
public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
- /** @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */
+ /** @hide Used to identify the volume of audio streams for enforced system sounds
+ * in certain countries (e.g camera in Japan) */
public static final int STREAM_SYSTEM_ENFORCED = AudioSystem.STREAM_SYSTEM_ENFORCED;
- /** The audio stream for DTMF Tones */
+ /** Used to identify the volume of audio streams for DTMF Tones */
public static final int STREAM_DTMF = AudioSystem.STREAM_DTMF;
- /** @hide The audio stream for text to speech (TTS) */
+ /** @hide Used to identify the volume of audio streams exclusively transmitted through the
+ * speaker (TTS) of the device */
public static final int STREAM_TTS = AudioSystem.STREAM_TTS;
/** Number of audio streams */
/**
- * @deprecated Use AudioSystem.getNumStreamTypes() instead
+ * @deprecated Do not iterate on volume stream type values.
*/
@Deprecated public static final int NUM_STREAMS = AudioSystem.NUM_STREAMS;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index f9bc95c..384be5c 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -39,27 +39,29 @@
* If these are modified, please also update Settings.System.VOLUME_SETTINGS
* and attrs.xml and AudioManager.java.
*/
- /* The default audio stream */
+ /** Used to identify the default audio stream volume */
public static final int STREAM_DEFAULT = -1;
- /* The audio stream for phone calls */
+ /** Used to identify the volume of audio streams for phone calls */
public static final int STREAM_VOICE_CALL = 0;
- /* The audio stream for system sounds */
+ /** Used to identify the volume of audio streams for system sounds */
public static final int STREAM_SYSTEM = 1;
- /* The audio stream for the phone ring and message alerts */
+ /** Used to identify the volume of audio streams for the phone ring and message alerts */
public static final int STREAM_RING = 2;
- /* The audio stream for music playback */
+ /** Used to identify the volume of audio streams for music playback */
public static final int STREAM_MUSIC = 3;
- /* The audio stream for alarms */
+ /** Used to identify the volume of audio streams for alarms */
public static final int STREAM_ALARM = 4;
- /* The audio stream for notifications */
+ /** Used to identify the volume of audio streams for notifications */
public static final int STREAM_NOTIFICATION = 5;
- /* @hide The audio stream for phone calls when connected on bluetooth */
+ /** Used to identify the volume of audio streams for phone calls when connected on bluetooth */
public static final int STREAM_BLUETOOTH_SCO = 6;
- /* @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */
+ /** Used to identify the volume of audio streams for enforced system sounds in certain
+ * countries (e.g camera in Japan) */
public static final int STREAM_SYSTEM_ENFORCED = 7;
- /* @hide The audio stream for DTMF tones */
+ /** Used to identify the volume of audio streams for DTMF tones */
public static final int STREAM_DTMF = 8;
- /* @hide The audio stream for text to speech (TTS) */
+ /** Used to identify the volume of audio streams exclusively transmitted through the
+ * speaker (TTS) of the device */
public static final int STREAM_TTS = 9;
/**
* @deprecated Use {@link #numStreamTypes() instead}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 69710d6..d77a082 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1764,16 +1764,13 @@
* <p>
* This method is only supported for JPEG files.
* </p>
- *
- * @throws UnsupportedOperationException If this method is called with unsupported files.
*/
public void saveAttributes() throws IOException {
if (!mIsSupportedFile || mMimeType != IMAGE_TYPE_JPEG) {
- throw new UnsupportedOperationException(
- "ExifInterface only supports saving attributes on JPEG formats.");
+ throw new IOException("ExifInterface only supports saving attributes on JPEG formats.");
}
if (mIsInputStream || (mSeekableFileDescriptor == null && mFilename == null)) {
- throw new UnsupportedOperationException(
+ throw new IOException(
"ExifInterface does not support saving attributes for the current input.");
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index abbace1..bd68fec 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -389,8 +389,8 @@
/** @hide Stream over a socket, limited to a single stream */
public static final int OUTPUT_FORMAT_RTP_AVP = 7;
- /** @hide H.264/AAC data encapsulated in MPEG2/TS */
- public static final int OUTPUT_FORMAT_MPEG2TS = 8;
+ /** H.264/AAC data encapsulated in MPEG2/TS */
+ public static final int MPEG_2_TS = 8;
/** VP8/VORBIS data in a WEBM container */
public static final int WEBM = 9;
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 96ea834..06dd3db 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -219,12 +219,25 @@
}
if (mBluetoothA2dpRoute != null) {
- if (mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) {
+ final boolean a2dpEnabled = isBluetoothA2dpOn();
+ if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
+ } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
+ a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
}
}
}
+ boolean isBluetoothA2dpOn() {
+ try {
+ return mAudioService.isBluetoothA2dpOn();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying Bluetooth A2DP state", e);
+ return false;
+ }
+ }
+
void updateDiscoveryRequest() {
// What are we looking for today?
int routeTypes = 0;
@@ -893,11 +906,6 @@
static void selectRouteStatic(int types, @NonNull RouteInfo route, boolean explicit) {
Log.v(TAG, "Selecting route: " + route);
assert(route != null);
- if (route == sStatic.mDefaultAudioVideo && sStatic.mBluetoothA2dpRoute != null) {
- Log.i(TAG, "Change the route to a BT route: " + sStatic.mBluetoothA2dpRoute
- + "\nDo not select the default route when a BT route is available.");
- route = sStatic.mBluetoothA2dpRoute;
- }
final RouteInfo oldRoute = sStatic.mSelectedRoute;
if (oldRoute == route) return;
if (!route.matchesTypes(types)) {
@@ -907,6 +915,16 @@
return;
}
+ final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
+ if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
+ (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
+ try {
+ sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error changing Bluetooth A2DP state", e);
+ }
+ }
+
final WifiDisplay activeDisplay =
sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
@@ -946,7 +964,7 @@
static void selectDefaultRouteStatic() {
// TODO: Be smarter about the route types here; this selects for all valid.
if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
- && sStatic.mBluetoothA2dpRoute != null) {
+ && sStatic.mBluetoothA2dpRoute != null && sStatic.isBluetoothA2dpOn()) {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
} else {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
@@ -1278,7 +1296,12 @@
selectedRoute == sStatic.mDefaultAudioVideo) {
dispatchRouteVolumeChanged(selectedRoute);
} else if (sStatic.mBluetoothA2dpRoute != null) {
- dispatchRouteVolumeChanged(sStatic.mBluetoothA2dpRoute);
+ try {
+ dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
+ sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
+ }
} else {
dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
}
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 8ae6e6b..7767712 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -371,6 +371,7 @@
private void destroyLocalPlayer() {
if (mLocalPlayer != null) {
+ mLocalPlayer.setOnCompletionListener(null);
mLocalPlayer.reset();
mLocalPlayer.release();
mLocalPlayer = null;
@@ -467,11 +468,12 @@
}
class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
- public void onCompletion(MediaPlayer mp)
- {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
synchronized (sActiveRingtones) {
sActiveRingtones.remove(Ringtone.this);
}
+ mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
}
}
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index af1410b..3cb01de 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -727,7 +727,9 @@
String setting = getSettingForType(type);
if (setting == null) return;
- ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
+ if(!isInternalRingtoneUri(ringtoneUri)) {
+ ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
+ }
Settings.System.putStringForUser(resolver, setting,
ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
@@ -744,6 +746,12 @@
}
}
+ private static boolean isInternalRingtoneUri(Uri uri) {
+ Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(uri);
+ return uriWithoutUserId == null ? false : uriWithoutUserId.toString()
+ .startsWith(MediaStore.Audio.Media.INTERNAL_CONTENT_URI.toString());
+ }
+
/**
* Try opening the given ringtone locally first, but failover to
* {@link IRingtonePlayer} if we can't access it directly. Typically happens
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index 66d1ed7..5fd9006 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -20,6 +20,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
+
/**
* This class contains information to describe a MIDI device.
* For now we only have information that can be retrieved easily for USB devices,
@@ -352,9 +354,15 @@
private Bundle getBasicProperties(String[] keys) {
Bundle basicProperties = new Bundle();
for (String key : keys) {
- String val = mProperties.getString(key);
+ Object val = mProperties.get(key);
if (val != null) {
- basicProperties.putString(key, val);
+ if (val instanceof String) {
+ basicProperties.putString(key, (String) val);
+ } else if (val instanceof Integer) {
+ basicProperties.putInt(key, (Integer) val);
+ } else {
+ Log.w(TAG, "Unsupported property type: " + val.getClass().getName());
+ }
}
}
return basicProperties;
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index 4e7551c..d6958b3 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -25,7 +25,9 @@
import android.os.ParcelFileDescriptor;
import android.os.UserManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import dalvik.system.CloseGuard;
import java.io.IOException;
@@ -45,6 +47,16 @@
System.loadLibrary("media_jni");
}
+ /** Make sure that MTP device is closed properly */
+ @GuardedBy("mLock")
+ private CloseGuard mCloseGuard = CloseGuard.get();
+
+ /** Current connection to the {@link #mDevice}, or null if device is not connected */
+ @GuardedBy("mLock")
+ private UsbDeviceConnection mConnection;
+
+ private final Object mLock = new Object();
+
/**
* MtpClient constructor
*
@@ -68,17 +80,25 @@
boolean result = false;
Context context = connection.getContext();
- if (context != null) {
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- if (!userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
- result = native_open(mDevice.getDeviceName(), connection.getFileDescriptor());
+ synchronized (mLock) {
+ if (context != null) {
+ UserManager userManager = (UserManager) context
+ .getSystemService(Context.USER_SERVICE);
+
+ if (!userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
+ result = native_open(mDevice.getDeviceName(), connection.getFileDescriptor());
+ }
+ }
+
+ if (!result) {
+ connection.close();
+ } else {
+ mConnection = connection;
+ mCloseGuard.open("close");
}
}
- if (!result) {
- connection.close();
- }
return result;
}
@@ -88,13 +108,23 @@
* with a new {@link android.hardware.usb.UsbDeviceConnection}.
*/
public void close() {
- native_close();
+ synchronized (mLock) {
+ if (mConnection != null) {
+ mCloseGuard.close();
+
+ native_close();
+
+ mConnection.close();
+ mConnection = null;
+ }
+ }
}
@Override
protected void finalize() throws Throwable {
try {
- native_close();
+ mCloseGuard.warnIfOpen();
+ close();
} finally {
super.finalize();
}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index dcd5057..faefc9f 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,7 +2,6 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- android_media_AmrInputStream.cpp \
android_media_ImageWriter.cpp \
android_media_ImageReader.cpp \
android_media_MediaCrypto.cpp \
@@ -32,7 +31,6 @@
libutils \
libbinder \
libmedia \
- libmediadrm \
libskia \
libui \
liblog \
@@ -42,13 +40,10 @@
libstagefright_foundation \
libcamera_client \
libmtp \
- libusbhost \
libexif \
- libpiex \
- libstagefright_amrnb_common
+ libpiex
LOCAL_STATIC_LIBRARIES := \
- libstagefright_amrnbenc
LOCAL_C_INCLUDES += \
external/libexif/ \
@@ -58,9 +53,6 @@
frameworks/base/libs/hwui \
frameworks/av/media/libmedia \
frameworks/av/media/libstagefright \
- frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
- frameworks/av/media/libstagefright/codecs/amrnb/common \
- frameworks/av/media/libstagefright/codecs/amrnb/common/include \
frameworks/av/media/mtp \
frameworks/native/include/media/openmax \
$(call include-path-for, libhardware)/hardware \
diff --git a/media/jni/android_media_AmrInputStream.cpp b/media/jni/android_media_AmrInputStream.cpp
deleted file mode 100644
index b56a364..0000000
--- a/media/jni/android_media_AmrInputStream.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
-**
-** 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.
-*/
-
-#define LOG_TAG "AmrInputStream"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "gsmamr_enc.h"
-
-// ----------------------------------------------------------------------------
-
-using namespace android;
-
-// Corresponds to max bit rate of 12.2 kbps.
-static const int MAX_OUTPUT_BUFFER_SIZE = 32;
-static const int FRAME_DURATION_MS = 20;
-static const int SAMPLING_RATE_HZ = 8000;
-static const int SAMPLES_PER_FRAME = ((SAMPLING_RATE_HZ * FRAME_DURATION_MS) / 1000);
-static const int BYTES_PER_SAMPLE = 2; // Assume 16-bit PCM samples
-static const int BYTES_PER_FRAME = (SAMPLES_PER_FRAME * BYTES_PER_SAMPLE);
-
-struct GsmAmrEncoderState {
- GsmAmrEncoderState()
- : mEncState(NULL),
- mSidState(NULL),
- mLastModeUsed(0) {
- }
-
- ~GsmAmrEncoderState() {}
-
- void* mEncState;
- void* mSidState;
- int32_t mLastModeUsed;
-};
-
-static jlong android_media_AmrInputStream_GsmAmrEncoderNew
- (JNIEnv *env, jclass /* clazz */) {
- GsmAmrEncoderState* gae = new GsmAmrEncoderState();
- if (gae == NULL) {
- jniThrowRuntimeException(env, "Out of memory");
- }
- return (jlong)gae;
-}
-
-static void android_media_AmrInputStream_GsmAmrEncoderInitialize
- (JNIEnv *env, jclass /* clazz */, jlong gae) {
- GsmAmrEncoderState *state = (GsmAmrEncoderState *) gae;
- int32_t nResult = AMREncodeInit(&state->mEncState, &state->mSidState, false);
- if (nResult != OK) {
- jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
- "GsmAmrEncoder initialization failed %d", nResult);
- }
-}
-
-static jint android_media_AmrInputStream_GsmAmrEncoderEncode
- (JNIEnv *env, jclass /* clazz */,
- jlong gae, jbyteArray pcm, jint pcmOffset, jbyteArray amr, jint amrOffset) {
-
- jbyte inBuf[BYTES_PER_FRAME];
- jbyte outBuf[MAX_OUTPUT_BUFFER_SIZE];
-
- env->GetByteArrayRegion(pcm, pcmOffset, sizeof(inBuf), inBuf);
- GsmAmrEncoderState *state = (GsmAmrEncoderState *) gae;
- int32_t length = AMREncode(state->mEncState, state->mSidState,
- (Mode) MR122,
- (int16_t *) inBuf,
- (unsigned char *) outBuf,
- (Frame_Type_3GPP*) &state->mLastModeUsed,
- AMR_TX_WMF);
- if (length < 0) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "Failed to encode a frame with error code: %d", length);
- return (jint)-1;
- }
-
- // The 1st byte of PV AMR frames are WMF (Wireless Multimedia Forum)
- // bitpacked, i.e.;
- // [P(4) + FT(4)]. Q=1 for good frame, P=padding bit, 0
- // Here we are converting the header to be as specified in Section 5.3 of
- // RFC 3267 (AMR storage format) i.e.
- // [P(1) + FT(4) + Q(1) + P(2)].
- if (length > 0) {
- outBuf[0] = (outBuf[0] << 3) | 0x4;
- }
-
- env->SetByteArrayRegion(amr, amrOffset, length, outBuf);
-
- return (jint)length;
-}
-
-static void android_media_AmrInputStream_GsmAmrEncoderCleanup
- (JNIEnv* /* env */, jclass /* clazz */, jlong gae) {
- GsmAmrEncoderState *state = (GsmAmrEncoderState *) gae;
- AMREncodeExit(&state->mEncState, &state->mSidState);
- state->mEncState = NULL;
- state->mSidState = NULL;
-}
-
-static void android_media_AmrInputStream_GsmAmrEncoderDelete
- (JNIEnv* /* env */, jclass /* clazz */, jlong gae) {
- delete (GsmAmrEncoderState*)gae;
-}
-
-// ----------------------------------------------------------------------------
-
-static const JNINativeMethod gMethods[] = {
- {"GsmAmrEncoderNew", "()J", (void*)android_media_AmrInputStream_GsmAmrEncoderNew},
- {"GsmAmrEncoderInitialize", "(J)V", (void*)android_media_AmrInputStream_GsmAmrEncoderInitialize},
- {"GsmAmrEncoderEncode", "(J[BI[BI)I", (void*)android_media_AmrInputStream_GsmAmrEncoderEncode},
- {"GsmAmrEncoderCleanup", "(J)V", (void*)android_media_AmrInputStream_GsmAmrEncoderCleanup},
- {"GsmAmrEncoderDelete", "(J)V", (void*)android_media_AmrInputStream_GsmAmrEncoderDelete},
-};
-
-
-int register_android_media_AmrInputStream(JNIEnv *env)
-{
- const char* const kClassPathName = "android/media/AmrInputStream";
-
- return AndroidRuntime::registerNativeMethods(env,
- kClassPathName, gMethods, NELEM(gMethods));
-}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 810996e..c2c66fd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -32,6 +32,7 @@
#include <gui/Surface.h>
#include <media/ICrypto.h>
+#include <media/MediaCodecBuffer.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -407,7 +408,7 @@
status_t JMediaCodec::getBuffers(
JNIEnv *env, bool input, jobjectArray *bufArray) const {
- Vector<sp<ABuffer> > buffers;
+ Vector<sp<MediaCodecBuffer> > buffers;
status_t err =
input
@@ -425,7 +426,7 @@
}
for (size_t i = 0; i < buffers.size(); ++i) {
- const sp<ABuffer> &buffer = buffers.itemAt(i);
+ const sp<MediaCodecBuffer> &buffer = buffers.itemAt(i);
jobject byteBuffer = NULL;
err = createByteBufferFromABuffer(
@@ -446,8 +447,9 @@
}
// static
+template <typename T>
status_t JMediaCodec::createByteBufferFromABuffer(
- JNIEnv *env, bool readOnly, bool clearBuffer, const sp<ABuffer> &buffer,
+ JNIEnv *env, bool readOnly, bool clearBuffer, const sp<T> &buffer,
jobject *buf) const {
// if this is an ABuffer that doesn't actually hold any accessible memory,
// use a null ByteBuffer
@@ -492,7 +494,7 @@
status_t JMediaCodec::getBuffer(
JNIEnv *env, bool input, size_t index, jobject *buf) const {
- sp<ABuffer> buffer;
+ sp<MediaCodecBuffer> buffer;
status_t err =
input
@@ -509,7 +511,7 @@
status_t JMediaCodec::getImage(
JNIEnv *env, bool input, size_t index, jobject *buf) const {
- sp<ABuffer> buffer;
+ sp<MediaCodecBuffer> buffer;
status_t err =
input
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index c0c47ef..5f0d3df 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -31,7 +31,7 @@
struct AMessage;
struct AString;
struct ICrypto;
-struct IGraphicBufferProducer;
+class IGraphicBufferProducer;
struct MediaCodec;
struct PersistentSurface;
class Surface;
@@ -146,8 +146,9 @@
status_t mInitStatus;
+ template <typename T>
status_t createByteBufferFromABuffer(
- JNIEnv *env, bool readOnly, bool clearBuffer, const sp<ABuffer> &buffer,
+ JNIEnv *env, bool readOnly, bool clearBuffer, const sp<T> &buffer,
jobject *buf) const;
void cacheJavaObjects(JNIEnv *env);
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index ecf733f..c825702 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1105,7 +1105,6 @@
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
-extern int register_android_media_AmrInputStream(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
@@ -1151,11 +1150,6 @@
goto bail;
}
- if (register_android_media_AmrInputStream(env) < 0) {
- ALOGE("ERROR: AmrInputStream native registration failed\n");
- goto bail;
- }
-
if (register_android_media_ResampleInputStream(env) < 0) {
ALOGE("ERROR: ResampleInputStream native registration failed\n");
goto bail;
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index 8bcc85f..768ac1d 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -194,6 +194,9 @@
return JNI_FALSE;
}
+ // The passed in fd is maintained by the UsbDeviceConnection
+ fd = dup(fd);
+
MtpDevice* device = MtpDevice::open(deviceNameStr, fd);
env->ReleaseStringUTFChars(deviceName, deviceNameStr);
diff --git a/media/jni/audioeffect/Android.mk b/media/jni/audioeffect/Android.mk
index 5c22c9b..91dd8a5 100644
--- a/media/jni/audioeffect/Android.mk
+++ b/media/jni/audioeffect/Android.mk
@@ -13,9 +13,6 @@
libnativehelper \
libmedia
-LOCAL_C_INCLUDES := \
- $(call include-path-for, audio-effects)
-
LOCAL_MODULE:= libaudioeffect_jni
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index a9b7062..401012f 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -352,7 +352,7 @@
effectCallback,
&lpJniStorage->mCallbackData,
(audio_session_t) sessionId,
- 0);
+ AUDIO_IO_HANDLE_NONE);
if (lpAudioEffect == 0) {
ALOGE("Error creating AudioEffect");
goto setup_failure;
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
index 20575e0..308c665 100644
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ b/media/tests/MediaFrameworkTest/Android.mk
@@ -7,8 +7,6 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_JAVA_LANGUAGE_VERSION := 1.8
-
LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-target-minus-junit4 \
android-support-test \
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/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
index cdd4065..195df78 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
@@ -415,7 +415,7 @@
in = getContext().getAssets().open(imageFile.getName());
ExifInterface exifInterface = new ExifInterface(in);
exifInterface.saveAttributes();
- } catch (UnsupportedOperationException e) {
+ } catch (IOException e) {
// Expected. saveAttributes is not supported with an ExifInterface object which was
// created with InputStream.
return;
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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 33d6b9a..662a1cd 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,7 @@
package com.android.externalstorage;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,7 @@
import android.os.storage.VolumeInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Path;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.provider.MediaStore;
@@ -48,6 +50,7 @@
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.Log;
+import android.util.Pair;
import android.webkit.MimeTypeMap;
import com.android.internal.annotations.GuardedBy;
@@ -183,7 +186,8 @@
root.rootId = rootId;
root.volumeId = volume.id;
root.flags = Root.FLAG_LOCAL_ONLY
- | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
+ | Root.FLAG_SUPPORTS_SEARCH
+ | Root.FLAG_SUPPORTS_IS_CHILD;
final DiskInfo disk = volume.getDisk();
if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk);
@@ -270,7 +274,6 @@
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
}
-
private String getDocIdForFile(File file) throws FileNotFoundException {
return getDocIdForFileMaybeCreate(file, false);
}
@@ -323,9 +326,19 @@
}
private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
+ RootInfo root = getRootFromDocId(docId);
+ return buildFile(root, docId, visible);
+ }
+
+ private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
+ throws FileNotFoundException {
+ RootInfo root = getRootFromDocId(docId);
+ return Pair.create(root, buildFile(root, docId, visible));
+ }
+
+ private RootInfo getRootFromDocId(String docId) throws FileNotFoundException {
final int splitIndex = docId.indexOf(':', 1);
final String tag = docId.substring(0, splitIndex);
- final String path = docId.substring(splitIndex + 1);
RootInfo root;
synchronized (mRootsLock) {
@@ -335,6 +348,14 @@
throw new FileNotFoundException("No root for " + tag);
}
+ return root;
+ }
+
+ private File buildFile(RootInfo root, String docId, boolean visible)
+ throws FileNotFoundException {
+ final int splitIndex = docId.indexOf(':', 1);
+ final String path = docId.substring(splitIndex + 1);
+
File target = visible ? root.visiblePath : root.path;
if (target == null) {
return null;
@@ -423,6 +444,36 @@
}
@Override
+ public Path findPath(String childDocId, @Nullable String parentDocId)
+ throws FileNotFoundException {
+ LinkedList<String> path = new LinkedList<>();
+
+ final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+ final RootInfo root = resolvedDocId.first;
+ File child = resolvedDocId.second;
+
+ final File parent = TextUtils.isEmpty(parentDocId)
+ ? root.path
+ : getFileForDocId(parentDocId);
+
+ if (!child.exists()) {
+ throw new FileNotFoundException(childDocId + " is not found.");
+ }
+
+ if (!child.getAbsolutePath().startsWith(parent.getAbsolutePath())) {
+ throw new FileNotFoundException(childDocId + " is not found under " + parentDocId);
+ }
+
+ while (child != null && child.getAbsolutePath().startsWith(parent.getAbsolutePath())) {
+ path.addFirst(getDocIdForFile(child));
+
+ child = child.getParentFile();
+ }
+
+ return new Path(parentDocId == null ? root.rootId : null, path);
+ }
+
+ @Override
public String createDocument(String docId, String mimeType, String displayName)
throws FileNotFoundException {
displayName = FileUtils.buildValidFatFilename(displayName);
diff --git a/packages/Keyguard/Android.mk b/packages/Keyguard/Android.mk
index f9e2686..38cf559 100644
--- a/packages/Keyguard/Android.mk
+++ b/packages/Keyguard/Android.mk
@@ -14,6 +14,17 @@
#
LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := SystemUI-tags
+
+LOCAL_SRC_FILES := src/com/android/systemui/EventLogTags.logtags
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# ------------------
+
include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
@@ -26,6 +37,8 @@
LOCAL_JAVA_LIBRARIES := SettingsLib
+LOCAL_STATIC_JAVA_LIBRARIES = SystemUI-tags
+
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/packages/Keyguard/res/values-b+sr+Latn/strings.xml b/packages/Keyguard/res/values-b+sr+Latn/strings.xml
index 570f4bc..8006125 100644
--- a/packages/Keyguard/res/values-b+sr+Latn/strings.xml
+++ b/packages/Keyguard/res/values-b+sr+Latn/strings.xml
@@ -56,7 +56,7 @@
<string name="kg_wrong_pattern" msgid="1850806070801358830">"Pogrešan šablon"</string>
<string name="kg_wrong_password" msgid="2333281762128113157">"Pogrešna lozinka"</string>
<string name="kg_wrong_pin" msgid="1131306510833563801">"Pogrešan PIN"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Probajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekunde(i)."</string>
<string name="kg_pattern_instructions" msgid="398978611683075868">"Nacrtajte šablon"</string>
<string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Unesite PIN SIM kartice"</string>
<string name="kg_sim_pin_instructions_multi" msgid="7818515973197201434">"Unesite PIN za SIM „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
@@ -72,9 +72,9 @@
<string name="kg_invalid_puk" msgid="3638289409676051243">"Ponovo unesite ispravni PUK kôd. Ponovljeni pokušaji će trajno onemogućiti SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodovi se ne podudaraju"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Previše pokušaja unosa šablona"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Uneli ste netačni PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Uneli ste netačnu lozinku <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Uneli ste netačni PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Uneli ste netačnu lozinku <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde(i)."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="8774056606869646621">"Pogrešno ste pokušali da otključate tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> put(a). Imate još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaj(a), nakon čega se tablet resetuje i svi podaci sa njega brišu."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="1843331751334128428">"Pogrešno ste pokušali da otključate telefon <xliff:g id="NUMBER_0">%1$d</xliff:g> put(a). Imate još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaj(a), nakon čega se telefon resetuje i svi podaci sa njega brišu."</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"Pogrešno ste pokušali da otključate tablet <xliff:g id="NUMBER">%d</xliff:g> put(a). Tablet će biti resetovan i svi podaci sa njega će biti izbrisani."</string>
@@ -87,8 +87,8 @@
<string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="6853071165802933545">"Pogrešno ste pokušali da otključate telefon <xliff:g id="NUMBER_0">%1$d</xliff:g> put(a). Imate još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaj(a), nakon čega se poslovni profil uklanja i svi podaci sa profila brišu."</string>
<string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"Pogrešno ste pokušali da otključate tablet <xliff:g id="NUMBER">%d</xliff:g> put(a). Poslovni profil će biti uklonjen i svi podaci sa njega će biti izbrisani."</string>
<string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4951507352869831265">"Pogrešno ste pokušali da otključate telefon <xliff:g id="NUMBER">%d</xliff:g> put(a). Poslovni profil će biti uklonjen i svi podaci sa njega će biti izbrisani."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću naloga e-pošte.\n\nPokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću naloga e-pošte.\n\nPokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde(i)."</string>
<string name="kg_password_wrong_pin_code_pukked" msgid="30531039455764924">"Netačan SIM PIN kôd. Sada morate da kontaktirate mobilnog operatera da biste otključali uređaj."</string>
<plurals name="kg_password_wrong_pin_code" formatted="false" msgid="6721575017538162249">
<item quantity="one">Netačan SIM PIN kôd. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaj.</item>
diff --git a/packages/Keyguard/res/values-da/strings.xml b/packages/Keyguard/res/values-da/strings.xml
index ebea6ee..b98a253 100644
--- a/packages/Keyguard/res/values-da/strings.xml
+++ b/packages/Keyguard/res/values-da/strings.xml
@@ -128,5 +128,5 @@
<item quantity="one">Enheden blev sidst låst op for <xliff:g id="NUMBER_1">%d</xliff:g> timer siden. Bekræft adgangskoden.</item>
<item quantity="other">Enheden blev sidst låst op for <xliff:g id="NUMBER_1">%d</xliff:g> timer siden. Bekræft adgangskoden.</item>
</plurals>
- <string name="fingerprint_not_recognized" msgid="2690661881608146617">"Kan ikke genkendes"</string>
+ <string name="fingerprint_not_recognized" msgid="2690661881608146617">"Ikke genkendt"</string>
</resources>
diff --git a/packages/Keyguard/res/values-sr/strings.xml b/packages/Keyguard/res/values-sr/strings.xml
index 840cae7..23c0b50 100644
--- a/packages/Keyguard/res/values-sr/strings.xml
+++ b/packages/Keyguard/res/values-sr/strings.xml
@@ -56,7 +56,7 @@
<string name="kg_wrong_pattern" msgid="1850806070801358830">"Погрешан шаблон"</string>
<string name="kg_wrong_password" msgid="2333281762128113157">"Погрешна лозинка"</string>
<string name="kg_wrong_pin" msgid="1131306510833563801">"Погрешан PIN"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Покушајте поново за <xliff:g id="NUMBER">%d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="6358110221603297548">"Пробајте поново за <xliff:g id="NUMBER">%d</xliff:g> секунде(и)."</string>
<string name="kg_pattern_instructions" msgid="398978611683075868">"Нацртајте шаблон"</string>
<string name="kg_sim_pin_instructions" msgid="2319508550934557331">"Унесите PIN SIM картице"</string>
<string name="kg_sim_pin_instructions_multi" msgid="7818515973197201434">"Унесите PIN за SIM „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
@@ -72,9 +72,9 @@
<string name="kg_invalid_puk" msgid="3638289409676051243">"Поново унесите исправни PUK кôд. Поновљени покушаји ће трајно онемогућити SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN кодови се не подударају"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Превише покушаја уноса шаблона"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Унели сте нетачни PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Унели сте нетачну лозинку <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПокушајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"Унели сте нетачни PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Унели сте нетачну лозинку <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде(и)."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="8774056606869646621">"Погрешно сте покушали да откључате таблет <xliff:g id="NUMBER_0">%1$d</xliff:g> пут(а). Имате још <xliff:g id="NUMBER_1">%2$d</xliff:g> покушај(а), након чега се таблет ресетује и сви подаци са њега бришу."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="1843331751334128428">"Погрешно сте покушали да откључате телефон <xliff:g id="NUMBER_0">%1$d</xliff:g> пут(а). Имате још <xliff:g id="NUMBER_1">%2$d</xliff:g> покушај(а), након чега се телефон ресетује и сви подаци са њега бришу."</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"Погрешно сте покушали да откључате таблет <xliff:g id="NUMBER">%d</xliff:g> пут(а). Таблет ће бити ресетован и сви подаци са њега ће бити избрисани."</string>
@@ -87,8 +87,8 @@
<string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="6853071165802933545">"Погрешно сте покушали да откључате телефон <xliff:g id="NUMBER_0">%1$d</xliff:g> пут(а). Имате још <xliff:g id="NUMBER_1">%2$d</xliff:g> покушај(а), након чега се пословни профил уклања и сви подаци са профила бришу."</string>
<string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"Погрешно сте покушали да откључате таблет <xliff:g id="NUMBER">%d</xliff:g> пут(а). Пословни профил ће бити уклоњен и сви подаци са њега ће бити избрисани."</string>
<string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4951507352869831265">"Погрешно сте покушали да откључате телефон <xliff:g id="NUMBER">%d</xliff:g> пут(а). Пословни профил ће бити уклоњен и сви подаци са њега ће бити избрисани."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу налога е-поште.\n\nПокушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу налога е-поште.\n\nПокушајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде(и)."</string>
<string name="kg_password_wrong_pin_code_pukked" msgid="30531039455764924">"Нетачан SIM PIN кôд. Сада морате да контактирате мобилног оператера да бисте откључали уређај."</string>
<plurals name="kg_password_wrong_pin_code" formatted="false" msgid="6721575017538162249">
<item quantity="one">Нетачан SIM PIN кôд. Имате још <xliff:g id="NUMBER_1">%d</xliff:g> покушај.</item>
diff --git a/packages/Keyguard/res/values-zh-rCN/strings.xml b/packages/Keyguard/res/values-zh-rCN/strings.xml
index 0723ab1..e15950f 100644
--- a/packages/Keyguard/res/values-zh-rCN/strings.xml
+++ b/packages/Keyguard/res/values-zh-rCN/strings.xml
@@ -36,7 +36,7 @@
<string name="keyguard_low_battery" msgid="8143808018719173859">"请连接充电器。"</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="1332288268600329841">"按“菜单”键解锁。"</string>
<string name="keyguard_network_locked_message" msgid="9169717779058037168">"网络已锁定"</string>
- <string name="keyguard_missing_sim_message_short" msgid="494980561304211931">"无 SIM 卡"</string>
+ <string name="keyguard_missing_sim_message_short" msgid="494980561304211931">"没有 SIM 卡"</string>
<string name="keyguard_missing_sim_message" product="tablet" msgid="1445849005909260039">"平板电脑中没有SIM卡。"</string>
<string name="keyguard_missing_sim_message" product="default" msgid="3481110395508637643">"手机中没有SIM卡。"</string>
<string name="keyguard_missing_sim_instructions" msgid="5210891509995942250">"请插入SIM卡。"</string>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 766eab7..94286ec 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -16,6 +16,9 @@
package com.android.keyguard;
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
import android.content.Context;
import android.os.AsyncTask;
import android.os.CountDownTimer;
@@ -132,6 +135,10 @@
return;
}
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
mPendingLockCheck = LockPatternChecker.checkPassword(
mLockPatternUtils,
entry,
@@ -140,12 +147,20 @@
@Override
public void onEarlyMatched() {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL);
+ }
onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPassword */);
}
@Override
public void onChecked(boolean matched, int timeoutMs) {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
setPasswordEntryInputEnabled(true);
mPendingLockCheck = null;
if (!matched) {
@@ -153,6 +168,16 @@
true /* isValidPassword */);
}
}
+
+ @Override
+ public void onCancelled() {
+ // We already got dismissed with the early matched callback, so we cancelled
+ // the check. However, we still need to note down the latency.
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ }
});
}
@@ -175,7 +200,7 @@
}
}
if (timeoutMs == 0) {
- mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true);
+ mSecurityMessageDisplay.setMessage(getWrongPasswordStringId());
}
}
resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
@@ -195,13 +220,13 @@
@Override
public void onTick(long millisUntilFinished) {
int secondsRemaining = (int) (millisUntilFinished / 1000);
- mSecurityMessageDisplay.setMessage(
- R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
+ mSecurityMessageDisplay.formatMessage(
+ R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
}
@Override
public void onFinish() {
- mSecurityMessageDisplay.setMessage("", false);
+ mSecurityMessageDisplay.setMessage("");
resetState();
}
}.start();
@@ -211,7 +236,7 @@
if (mCallback != null) {
mCallback.userActivity();
}
- mSecurityMessageDisplay.setMessage("", false);
+ mSecurityMessageDisplay.setMessage("");
}
@Override
@@ -248,8 +273,7 @@
if (reason != PROMPT_REASON_NONE) {
int promtReasonStringRes = getPromtReasonStringRes(reason);
if (promtReasonStringRes != 0) {
- mSecurityMessageDisplay.setMessage(promtReasonStringRes,
- true /* important */);
+ mSecurityMessageDisplay.setMessage(promtReasonStringRes);
}
}
}
@@ -257,7 +281,7 @@
@Override
public void showMessage(String message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
- mSecurityMessageDisplay.setMessage(message, true /* important */);
+ mSecurityMessageDisplay.setMessage(message);
}
protected abstract int getPromtReasonStringRes(int reason);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index c8adf64..d19821f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -41,25 +41,12 @@
private static final long ANNOUNCEMENT_DELAY = 250;
private static final int DEFAULT_COLOR = -1;
- private static final int SECURITY_MESSAGE_DURATION = 5000;
-
- private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mHandler;
private final int mDefaultColor;
- // Timeout before we reset the message to show charging/owner info
- long mTimeout = SECURITY_MESSAGE_DURATION;
- 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);
@@ -74,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();
@@ -91,8 +81,8 @@
}
@Override
- public void setMessage(CharSequence msg, boolean important) {
- if (!TextUtils.isEmpty(msg) && important) {
+ public void setMessage(CharSequence msg) {
+ if (!TextUtils.isEmpty(msg)) {
securityMessageChanged(msg);
} else {
clearMessage();
@@ -100,28 +90,21 @@
}
@Override
- public void setMessage(int resId, boolean important) {
- if (resId != 0 && important) {
- CharSequence message = getContext().getResources().getText(resId);
- securityMessageChanged(message);
- } else {
- clearMessage();
+ public void setMessage(int resId) {
+ CharSequence message = null;
+ if (resId != 0) {
+ message = getContext().getResources().getText(resId);
}
+ setMessage(message);
}
@Override
- public void setMessage(int resId, boolean important, Object... formatArgs) {
- if (resId != 0 && important) {
- String message = getContext().getString(resId, formatArgs);
- securityMessageChanged(message);
- } else {
- clearMessage();
+ public void formatMessage(int resId, Object... formatArgs) {
+ CharSequence message = null;
+ if (resId != 0) {
+ message = getContext().getString(resId, formatArgs);
}
- }
-
- @Override
- public void setTimeout(int timeoutMs) {
- mTimeout = timeoutMs;
+ setMessage(message);
}
public static SecurityMessageDisplay findSecurityMessageDisplay(View v) {
@@ -142,18 +125,14 @@
private void securityMessageChanged(CharSequence message) {
mMessage = message;
update();
- mHandler.removeCallbacks(mClearMessageRunnable);
- if (mTimeout > 0) {
- mHandler.postDelayed(mClearMessageRunnable, mTimeout);
- }
mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
(SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
}
private void clearMessage() {
- mHandler.removeCallbacks(mClearMessageRunnable);
- mHandler.post(mClearMessageRunnable);
+ mMessage = null;
+ update();
}
private void update() {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
index 285b1ae..590d8d5 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
@@ -67,7 +67,7 @@
@Override
protected void resetState() {
super.resetState();
- mSecurityMessageDisplay.setMessage(R.string.kg_pin_instructions, false);
+ mSecurityMessageDisplay.setMessage("");
}
@Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
index ddccc14..d49ff97 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
@@ -79,7 +79,7 @@
@Override
protected void resetState() {
- mSecurityMessageDisplay.setMessage(R.string.kg_password_instructions, false);
+ mSecurityMessageDisplay.setMessage("");
final boolean wasDisabled = mPasswordEntry.isEnabled();
setPasswordEntryEnabled(true);
setPasswordEntryInputEnabled(true);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index 7d1a6fb..330632b 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,6 +15,9 @@
*/
package com.android.keyguard;
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.AsyncTask;
@@ -197,7 +200,7 @@
}
private void displayDefaultSecurityMessage() {
- mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
+ mSecurityMessageDisplay.setMessage("");
}
@Override
@@ -216,7 +219,7 @@
@Override
public void onPatternStart() {
mLockPatternView.removeCallbacks(mCancelPatternRunnable);
- mSecurityMessageDisplay.setMessage("", false);
+ mSecurityMessageDisplay.setMessage("");
}
@Override
@@ -242,6 +245,10 @@
return;
}
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
mPendingLockCheck = LockPatternChecker.checkPattern(
mLockPatternUtils,
pattern,
@@ -250,12 +257,20 @@
@Override
public void onEarlyMatched() {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL);
+ }
onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPattern */);
}
@Override
public void onChecked(boolean matched, int timeoutMs) {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
mLockPatternView.enableInput();
mPendingLockCheck = null;
if (!matched) {
@@ -263,6 +278,16 @@
true /* isValidPattern */);
}
}
+
+ @Override
+ public void onCancelled() {
+ // We already got dismissed with the early matched callback, so we
+ // cancelled the check. However, we still need to note down the latency.
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ }
});
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity();
@@ -289,7 +314,7 @@
}
}
if (timeoutMs == 0) {
- mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
+ mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern);
mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
}
}
@@ -306,8 +331,8 @@
@Override
public void onTick(long millisUntilFinished) {
final int secondsRemaining = (int) (millisUntilFinished / 1000);
- mSecurityMessageDisplay.setMessage(
- R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
+ mSecurityMessageDisplay.formatMessage(
+ R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
}
@Override
@@ -350,26 +375,21 @@
public void showPromptReason(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern,
- true /* important */);
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern);
break;
case PROMPT_REASON_TIMEOUT:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern,
- true /* important */);
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
case PROMPT_REASON_DEVICE_ADMIN:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin,
- true /* important */);
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin);
break;
case PROMPT_REASON_USER_REQUEST:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request,
- true /* important */);
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
break;
case PROMPT_REASON_NONE:
break;
default:
- mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern,
- true /* important */);
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
}
}
@@ -377,7 +397,7 @@
@Override
public void showMessage(String message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
- mSecurityMessageDisplay.setMessage(message, true /* important */);
+ mSecurityMessageDisplay.setMessage(message);
}
@Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 04f32a1..91c943d 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -174,6 +174,7 @@
final AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(title)
.setMessage(message)
+ .setCancelable(false)
.setNeutralButton(R.string.ok, null)
.create();
if (!(mContext instanceof Activity)) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
index d48cc94..839d3ce 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
@@ -92,7 +92,7 @@
color = info.getIconTint();
}
}
- mSecurityMessageDisplay.setMessage(msg, true);
+ mSecurityMessageDisplay.setMessage(msg);
mSimImageView.setImageTintList(ColorStateList.valueOf(color));
}
}
@@ -141,7 +141,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
- mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default
if (mEcaView instanceof EmergencyCarrierArea) {
((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
}
@@ -252,7 +251,7 @@
if (entry.length() < 4) {
// otherwise, display a message to the user, and don't submit.
- mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint, true);
+ mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint);
resetPasswordText(true /* animate */, true /* announce */);
mCallback.userActivity();
return;
@@ -284,13 +283,13 @@
} else {
// show message
mSecurityMessageDisplay.setMessage(
- getPinPasswordErrorMessage(attemptsRemaining), true);
+ getPinPasswordErrorMessage(attemptsRemaining));
}
} else {
// "PIN operation failed!" - no idea what this was and no way to
// find out. :/
mSecurityMessageDisplay.setMessage(getContext().getString(
- R.string.kg_password_pin_failed), true);
+ R.string.kg_password_pin_failed));
}
if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+ " CheckSimPin.onSimCheckResponse: " + result
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
index 249dde8..3871448 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
@@ -108,7 +108,7 @@
}
resetPasswordText(true /* animate */, true /* announce */);
if (msg != 0) {
- mSecurityMessageDisplay.setMessage(msg, true);
+ mSecurityMessageDisplay.setMessage(msg);
}
}
@@ -133,7 +133,7 @@
color = info.getIconTint();
}
}
- mSecurityMessageDisplay.setMessage(msg, true);
+ mSecurityMessageDisplay.setMessage(msg);
mSimImageView.setImageTintList(ColorStateList.valueOf(color));
}
mPasswordEntry.requestFocus();
@@ -184,7 +184,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
- mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default
if (mEcaView instanceof EmergencyCarrierArea) {
((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
}
@@ -341,11 +340,11 @@
} else {
// show message
mSecurityMessageDisplay.setMessage(
- getPukPasswordErrorMessage(attemptsRemaining), true);
+ getPukPasswordErrorMessage(attemptsRemaining));
}
} else {
mSecurityMessageDisplay.setMessage(getContext().getString(
- R.string.kg_password_puk_failed), true);
+ R.string.kg_password_puk_failed));
}
if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+ " UpdateSim.onSimCheckResponse: "
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 288b954..66e56e0 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
@@ -136,6 +137,7 @@
private static final int MSG_SCREEN_TURNED_ON = 331;
private static final int MSG_SCREEN_TURNED_OFF = 332;
private static final int MSG_DREAMING_STATE_CHANGED = 333;
+ private static final int MSG_USER_UNLOCKED = 334;
/** Fingerprint state: Not listening to fingerprint. */
private static final int FINGERPRINT_STATE_STOPPED = 0;
@@ -291,6 +293,9 @@
case MSG_DREAMING_STATE_CHANGED:
handleDreamingStateChanged(msg.arg1);
break;
+ case MSG_USER_UNLOCKED:
+ handleUserUnlocked();
+ break;
}
}
};
@@ -723,6 +728,8 @@
} else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
.equals(action)) {
mHandler.sendEmptyMessage(MSG_DPM_STATE_CHANGED);
+ } else if (ACTION_USER_UNLOCKED.equals(action)) {
+ mHandler.sendEmptyMessage(MSG_USER_UNLOCKED);
}
}
};
@@ -1025,6 +1032,16 @@
}
}
+ private void handleUserUnlocked() {
+ mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserUnlocked();
+ }
+ }
+ }
+
private KeyguardUpdateMonitor(Context context) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
@@ -1065,6 +1082,7 @@
allUserFilter.addAction(ACTION_FACE_UNLOCK_STARTED);
allUserFilter.addAction(ACTION_FACE_UNLOCK_STOPPED);
allUserFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ allUserFilter.addAction(ACTION_USER_UNLOCKED);
context.registerReceiverAsUser(mBroadcastAllReceiver, UserHandle.ALL, allUserFilter,
null, null);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index eb29d9b..14d6b59 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -128,6 +128,11 @@
public void onUserInfoChanged(int userId) { }
/**
+ * Called when a user got unlocked.
+ */
+ public void onUserUnlocked() { }
+
+ /**
* Called when boot completed.
*
* Note, this callback will only be received if boot complete occurs after registering with
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTracker.java b/packages/Keyguard/src/com/android/keyguard/LatencyTracker.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/LatencyTracker.java
rename to packages/Keyguard/src/com/android/keyguard/LatencyTracker.java
index 0196815..cee0afc 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTracker.java
+++ b/packages/Keyguard/src/com/android/keyguard/LatencyTracker.java
@@ -14,14 +14,13 @@
* limitations under the License
*/
-package com.android.systemui;
+package com.android.keyguard;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
-import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
@@ -29,9 +28,15 @@
import android.util.Log;
import android.util.SparseLongArray;
+import com.android.systemui.EventLogTags;
+
/**
* Class to track various latencies in SystemUI. It then outputs the latency to logcat so these
* latencies can be captured by tests and then used for dashboards.
+ * <p>
+ * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
+ * eventually we'd want to merge these two packages together so Keyguard can use common classes
+ * that are shared with SystemUI.
*/
public class LatencyTracker {
@@ -55,10 +60,29 @@
*/
public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2;
+ /**
+ * Time it takes to check PIN/Pattern/Password.
+ */
+ public static final int ACTION_CHECK_CREDENTIAL = 3;
+
+ /**
+ * Time it takes to check fully PIN/Pattern/Password, i.e. that's the time spent including the
+ * actions to unlock a user.
+ */
+ public static final int ACTION_CHECK_CREDENTIAL_UNLOCKED = 4;
+
+ /**
+ * Time it takes to turn on the screen.
+ */
+ public static final int ACTION_TURN_ON_SCREEN = 5;
+
private static final String[] NAMES = new String[] {
"expand panel",
"toggle recents",
- "fingerprint wake-and-unlock" };
+ "fingerprint wake-and-unlock",
+ "check credential",
+ "check credential unlocked",
+ "turn on screen" };
private static LatencyTracker sLatencyTracker;
@@ -117,6 +141,7 @@
if (startRtc == -1) {
return;
}
+ mStartRtc.delete(action);
Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0);
long duration = endRtc - startRtc;
Log.i(TAG, "action=" + action + " latency=" + duration);
diff --git a/packages/Keyguard/src/com/android/keyguard/SecurityMessageDisplay.java b/packages/Keyguard/src/com/android/keyguard/SecurityMessageDisplay.java
index ddb1f6e..6977b51 100644
--- a/packages/Keyguard/src/com/android/keyguard/SecurityMessageDisplay.java
+++ b/packages/Keyguard/src/com/android/keyguard/SecurityMessageDisplay.java
@@ -20,11 +20,9 @@
void setNextMessageColor(int color);
- void setMessage(CharSequence msg, boolean important);
+ void setMessage(CharSequence msg);
- void setMessage(int resId, boolean important);
+ void setMessage(int resId);
- void setMessage(int resId, boolean important, Object... formatArgs);
-
- void setTimeout(int timeout_ms);
+ void formatMessage(int resId, Object... formatArgs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/Keyguard/src/com/android/systemui/EventLogTags.logtags
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
rename to packages/Keyguard/src/com/android/systemui/EventLogTags.logtags
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
index a2e9e94..e948cf7 100644
--- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -23,6 +23,7 @@
#include <linux/fuse.h>
#include <sys/stat.h>
+#include <sys/uio.h>
#include <map>
diff --git a/packages/MtpDocumentsProvider/perf_tests/Android.mk b/packages/MtpDocumentsProvider/perf_tests/Android.mk
new file mode 100644
index 0000000..f0d4878
--- /dev/null
+++ b/packages/MtpDocumentsProvider/perf_tests/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_PACKAGE_NAME := MtpDocumentsProviderPerfTests
+LOCAL_INSTRUMENTATION_FOR := MtpDocumentsProvider
+LOCAL_CERTIFICATE := media
+
+include $(BUILD_PACKAGE)
diff --git a/packages/MtpDocumentsProvider/perf_tests/AndroidManifest.xml b/packages/MtpDocumentsProvider/perf_tests/AndroidManifest.xml
new file mode 100644
index 0000000..26e109d
--- /dev/null
+++ b/packages/MtpDocumentsProvider/perf_tests/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.mtp.perftests"
+ android:sharedUserId="android.media">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.mtp"
+ android:label="Performance tests for MtpDocumentsProvider." />
+</manifest>
diff --git a/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java b/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java
new file mode 100644
index 0000000..0762571
--- /dev/null
+++ b/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.mtp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.support.test.filters.LargeTest;
+import android.support.test.InstrumentationRegistry;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import libcore.io.IoUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.Test;
+
+@RunWith(JUnit4.class)
+public class AppFusePerfTest {
+ @Test
+ @LargeTest
+ public void testReadWriteFile() throws IOException {
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final StorageManager storageManager = context.getSystemService(StorageManager.class);
+ final int INODE = 10;
+ final int SIZE = 10 * 1024 * 1024; // 10MB
+ final AppFuse appFuse = new AppFuse(
+ "test",
+ new TestCallback() {
+ @Override
+ public long getFileSize(int inode) throws FileNotFoundException {
+ if (inode != INODE) {
+ throw new FileNotFoundException();
+ }
+ return SIZE;
+ }
+
+ @Override
+ public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
+ throws IOException {
+ return size;
+ }
+
+ @Override
+ public int writeObjectBytes(
+ long fileHandle, int inode, long offset, int size, byte[] bytes) {
+ return size;
+ }
+ });
+
+ appFuse.mount(storageManager);
+
+ final byte[] bytes = new byte[SIZE];
+ final int SAMPLES = 100;
+ final double[] readTime = new double[SAMPLES];
+ final double[] writeTime = new double[SAMPLES];
+
+ for (int i = 0; i < SAMPLES; i++) {
+ final ParcelFileDescriptor fd = appFuse.openFile(
+ INODE,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ try (final ParcelFileDescriptor.AutoCloseInputStream stream =
+ new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+ final long startTime = System.nanoTime();
+ stream.read(bytes);
+ readTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
+ }
+
+ }
+
+ for (int i = 0; i < SAMPLES; i++) {
+ final ParcelFileDescriptor fd = appFuse.openFile(
+ INODE,
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
+ try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+ final long startTime = System.nanoTime();
+ stream.write(bytes);
+ writeTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
+ }
+ }
+
+ appFuse.close();
+
+ double readAverage = 0;
+ double writeAverage = 0;
+ double readSquaredAverage = 0;
+ double writeSquaredAverage = 0;
+ for (int i = 0; i < SAMPLES; i++) {
+ readAverage += readTime[i];
+ writeAverage += writeTime[i];
+ readSquaredAverage += readTime[i] * readTime[i];
+ writeSquaredAverage += writeTime[i] * writeTime[i];
+ }
+
+ readAverage /= SAMPLES;
+ writeAverage /= SAMPLES;
+ readSquaredAverage /= SAMPLES;
+ writeSquaredAverage /= SAMPLES;
+
+ final Bundle results = new Bundle();
+ results.putDouble("readAverage", readAverage);
+ results.putDouble("readStandardDeviation",
+ Math.sqrt(readSquaredAverage - readAverage * readAverage));
+ results.putDouble("writeAverage", writeAverage);
+ results.putDouble("writeStandardDeviation",
+ Math.sqrt(writeSquaredAverage - writeAverage * writeAverage));
+ InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, results);
+ }
+
+ private static class TestCallback implements AppFuse.Callback {
+ @Override
+ public long getFileSize(int inode) throws FileNotFoundException {
+ throw new FileNotFoundException();
+ }
+
+ @Override
+ public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
+ throws IOException {
+ throw new IOException();
+ }
+
+ @Override
+ public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
+ throws IOException {
+ throw new IOException();
+ }
+
+ @Override
+ public void flushFileHandle(long fileHandle) throws IOException {}
+
+ @Override
+ public void closeFileHandle(long fileHandle) {}
+ }
+}
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 2da4d6a..4b9415e 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -71,6 +71,7 @@
android:name=".ui.SelectPrinterActivity"
android:label="@string/all_printers_label"
android:theme="@style/Theme.SelectPrinterActivity"
+ android:parentActivityName=".ui.PrintActivity"
android:exported="false">
</activity>
@@ -78,6 +79,7 @@
android:name=".ui.AddPrinterActivity"
android:label="@string/print_add_printer"
android:theme="@style/Theme.AddPrinterActivity"
+ android:parentActivityName=".ui.SelectPrinterActivity"
android:exported="false">
</activity>
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 31a776c..94519d4 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -49,8 +49,10 @@
android:id="@+id/summary_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:paddingTop="2dip"
android:paddingStart="16dip"
android:paddingEnd="16dip"
+ android:paddingBottom="8dip"
android:orientation="horizontal"
android:elevation="@dimen/preview_controls_elevation"
android:background="?android:attr/colorPrimary">
diff --git a/packages/PrintSpooler/res/values-b+sr+Latn/strings.xml b/packages/PrintSpooler/res/values-b+sr+Latn/strings.xml
index ce8a81e..2b1b8ca 100644
--- a/packages/PrintSpooler/res/values-b+sr+Latn/strings.xml
+++ b/packages/PrintSpooler/res/values-b+sr+Latn/strings.xml
@@ -103,8 +103,8 @@
<item msgid="3199660090246166812">"Vodoravno"</item>
</string-array>
<string name="print_write_error_message" msgid="5787642615179572543">"Upisivanje u datoteku nije moguće"</string>
- <string name="print_error_default_message" msgid="8602678405502922346">"Žao nam je, ovo nije uspelo. Pokušajte ponovo."</string>
- <string name="print_error_retry" msgid="1426421728784259538">"Pokušajte ponovo"</string>
+ <string name="print_error_default_message" msgid="8602678405502922346">"Žao nam je, ovo nije uspelo. Probajte ponovo."</string>
+ <string name="print_error_retry" msgid="1426421728784259538">"Probajte ponovo"</string>
<string name="print_error_printer_unavailable" msgid="8985614415253203381">"Ovaj štampač trenutno nije dostupan."</string>
<string name="print_cannot_load_page" msgid="6179560924492912009">"Nije uspeo prikaz pregleda"</string>
<string name="print_preparing_preview" msgid="3939930735671364712">"Priprema pregleda..."</string>
diff --git a/packages/PrintSpooler/res/values-kn-rIN/strings.xml b/packages/PrintSpooler/res/values-kn-rIN/strings.xml
index ef3ea16..64cb540 100644
--- a/packages/PrintSpooler/res/values-kn-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-kn-rIN/strings.xml
@@ -47,7 +47,7 @@
<string name="savetopdf_button" msgid="2976186791686924743">"PDF ಗೆ ಉಳಿಸು"</string>
<string name="print_options_expanded" msgid="6944679157471691859">"ಪ್ರಿಂಟ್ ಆಯ್ಕೆಗಳನ್ನು ವಿಸ್ತರಿಸಲಾಗಿದೆ"</string>
<string name="print_options_collapsed" msgid="7455930445670414332">"ಪ್ರಿಂಟ್ ಆಯ್ಕೆಗಳನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
- <string name="search" msgid="5421724265322228497">"ಹುಡುಕು"</string>
+ <string name="search" msgid="5421724265322228497">"ಹುಡುಕಿ"</string>
<string name="all_printers_label" msgid="3178848870161526399">"ಎಲ್ಲಾ ಪ್ರಿಂಟರ್ಗಳು"</string>
<string name="add_print_service_label" msgid="5356702546188981940">"ಸೇವೆಯನ್ನು ಸೇರಿಸು"</string>
<string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ಹುಡುಕಾಟ ಪೆಟ್ಟಿಗೆಯನ್ನು ತೋರಿಸಲಾಗಿದೆ"</string>
@@ -81,7 +81,7 @@
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ರದ್ದು ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="failed_notification_title_template" msgid="2256217208186530973">"ಮುದ್ರಕ ದೋಷ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="blocked_notification_title_template" msgid="1175435827331588646">"ಮುದ್ರಕವು <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ನಿರ್ಬಂಧಿಸಿದೆ"</string>
- <string name="cancel" msgid="4373674107267141885">"ರದ್ದುಮಾಡು"</string>
+ <string name="cancel" msgid="4373674107267141885">"ರದ್ದುಮಾಡಿ"</string>
<string name="restart" msgid="2472034227037808749">"ಮರುಪ್ರಾರಂಭಿಸು"</string>
<string name="no_connection_to_printer" msgid="2159246915977282728">"ಮುದ್ರಕಕ್ಕೆ ಸಂಪರ್ಕವಿಲ್ಲ"</string>
<string name="reason_unknown" msgid="5507940196503246139">"ಅಪರಿಚಿತ"</string>
diff --git a/packages/PrintSpooler/res/values-sr/strings.xml b/packages/PrintSpooler/res/values-sr/strings.xml
index 012bbbc..9283d77 100644
--- a/packages/PrintSpooler/res/values-sr/strings.xml
+++ b/packages/PrintSpooler/res/values-sr/strings.xml
@@ -103,8 +103,8 @@
<item msgid="3199660090246166812">"Водоравно"</item>
</string-array>
<string name="print_write_error_message" msgid="5787642615179572543">"Уписивање у датотеку није могуће"</string>
- <string name="print_error_default_message" msgid="8602678405502922346">"Жао нам је, ово није успело. Покушајте поново."</string>
- <string name="print_error_retry" msgid="1426421728784259538">"Покушајте поново"</string>
+ <string name="print_error_default_message" msgid="8602678405502922346">"Жао нам је, ово није успело. Пробајте поново."</string>
+ <string name="print_error_retry" msgid="1426421728784259538">"Пробајте поново"</string>
<string name="print_error_printer_unavailable" msgid="8985614415253203381">"Овај штампач тренутно није доступан."</string>
<string name="print_cannot_load_page" msgid="6179560924492912009">"Није успео приказ прегледа"</string>
<string name="print_preparing_preview" msgid="3939930735671364712">"Припрема прегледа..."</string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index f688a8e..23c6615 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -46,7 +46,6 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.print.IPrintDocumentAdapter;
import android.print.PageRange;
@@ -760,6 +759,13 @@
mPrintJob.setPrinterId(printerInfo.getId());
mPrintJob.setPrinterName(printerInfo.getName());
+ if (canPrint(printerInfo)) {
+ updatePrintAttributesFromCapabilities(printerInfo.getCapabilities());
+ onPrinterAvailable(printerInfo);
+ } else {
+ onPrinterUnavailable(printerInfo);
+ }
+
mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo);
MetricsLogger.action(this, MetricsEvent.ACTION_PRINTER_SELECT_ALL,
@@ -2046,7 +2052,7 @@
}
public void onPrinterUnavailable(PrinterInfo printer) {
- if (mCurrentPrinter.getId().equals(printer.getId())) {
+ if (mCurrentPrinter == null || mCurrentPrinter.getId().equals(printer.getId())) {
setState(STATE_PRINTER_UNAVAILABLE);
mPrintedDocument.cancel(false);
ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
@@ -2305,8 +2311,7 @@
public int getPrinterIndex(PrinterId printerId) {
for (int i = 0; i < getCount(); i++) {
PrinterHolder printerHolder = (PrinterHolder) getItem(i);
- if (printerHolder != null && !printerHolder.removed
- && printerHolder.printer.getId().equals(printerId)) {
+ if (printerHolder != null && printerHolder.printer.getId().equals(printerId)) {
return i;
}
}
@@ -2535,7 +2540,11 @@
if (updatedPrinter != null) {
printerHolder.printer = updatedPrinter;
printerHolder.removed = false;
- onPrinterAvailable(printerHolder.printer);
+ if (canPrint(printerHolder.printer)) {
+ onPrinterAvailable(printerHolder.printer);
+ } else {
+ onPrinterUnavailable(printerHolder.printer);
+ }
newPrinterHolders.add(printerHolder);
} else if (mCurrentPrinter != null && mCurrentPrinter.getId().equals(oldPrinterId)){
printerHolder.removed = true;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index cc69089..74582f3 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -17,6 +17,7 @@
package com.android.printspooler.ui;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.ComponentName;
@@ -24,13 +25,17 @@
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.Loader;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Bundle;
import android.print.PrintManager;
import android.print.PrintServicesLoader;
import android.print.PrinterId;
import android.print.PrinterInfo;
+import android.printservice.PrintService;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
@@ -76,6 +81,8 @@
private static final int LOADER_ID_PRINT_REGISTRY_INT = 2;
private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 3;
+ private static final int INFO_INTENT_REQUEST_CODE = 1;
+
public static final String INTENT_EXTRA_PRINTER = "INTENT_EXTRA_PRINTER";
private static final String EXTRA_PRINTER = "EXTRA_PRINTER";
@@ -83,6 +90,7 @@
private static final String KEY_NOT_FIRST_CREATE = "KEY_NOT_FIRST_CREATE";
private static final String KEY_DID_SEARCH = "DID_SEARCH";
+ private static final String KEY_PRINTER_FOR_INFO_INTENT = "KEY_PRINTER_FOR_INFO_INTENT";
// Constants for MetricsLogger.count and MetricsLogger.histo
private static final String PRINTERS_LISTED_COUNT = "printers_listed";
@@ -100,6 +108,12 @@
private boolean mDidSearch;
+ /**
+ * Printer we are currently in the info intent for. This is only non-null while this activity
+ * started an info intent that has not yet returned
+ */
+ private @Nullable PrinterInfo mPrinterForInfoIntent;
+
private void startAddPrinterActivity() {
MetricsLogger.action(this, MetricsEvent.ACTION_PRINT_SERVICE_ADD);
startActivity(new Intent(this, AddPrinterActivity.class));
@@ -112,6 +126,8 @@
setContentView(R.layout.select_printer_activity);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
mEnabledPrintServices = new ArrayMap<>();
mPrinterRegistry = new PrinterRegistry(this, null, LOADER_ID_PRINT_REGISTRY,
@@ -200,6 +216,7 @@
if (savedInstanceState != null) {
mDidSearch = savedInstanceState.getBoolean(KEY_DID_SEARCH);
+ mPrinterForInfoIntent = savedInstanceState.getParcelable(KEY_PRINTER_FOR_INFO_INTENT);
}
}
@@ -208,6 +225,7 @@
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_NOT_FIRST_CREATE, true);
outState.putBoolean(KEY_DID_SEARCH, mDidSearch);
+ outState.putParcelable(KEY_PRINTER_FOR_INFO_INTENT, mPrinterForInfoIntent);
}
@Override
@@ -252,6 +270,16 @@
}
@Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
if (view == mListView) {
final int position = ((AdapterContextMenuInfo) menuInfo).position;
@@ -353,6 +381,24 @@
super.onDestroy();
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case INFO_INTENT_REQUEST_CODE:
+ if (resultCode == RESULT_OK &&
+ data != null &&
+ data.getBooleanExtra(PrintService.EXTRA_SELECT_PRINTER, false) &&
+ mPrinterForInfoIntent != null &&
+ mPrinterForInfoIntent.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
+ onPrinterSelected(mPrinterForInfoIntent);
+ }
+ mPrinterForInfoIntent = null;
+ break;
+ default:
+ // not reached
+ }
+ }
+
private void onPrinterSelected(PrinterInfo printer) {
Intent intent = new Intent();
intent.putExtra(INTENT_EXTRA_PRINTER, printer);
@@ -418,6 +464,26 @@
}
}
+ /**
+ * Return the target SDK of the package that defined the printer.
+ *
+ * @param printer The printer
+ *
+ * @return The target SDK that defined a printer.
+ */
+ private int getTargetSDKOfPrintersService(@NonNull PrinterInfo printer) {
+ ApplicationInfo serviceAppInfo;
+ try {
+ serviceAppInfo = getPackageManager().getApplicationInfo(
+ printer.getId().getServiceName().getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Could not find package that defined the printer", e);
+ return Build.VERSION_CODES.KITKAT;
+ }
+
+ return serviceAppInfo.targetSdkVersion;
+ }
+
private final class DestinationAdapter extends BaseAdapter implements Filterable {
private final Object mLock = new Object();
@@ -638,15 +704,17 @@
LinearLayout moreInfoView = (LinearLayout) convertView.findViewById(R.id.more_info);
if (printer.getInfoIntent() != null) {
moreInfoView.setVisibility(View.VISIBLE);
- moreInfoView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- try {
- startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0,
- 0);
- } catch (SendIntentException e) {
- Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
- }
+ moreInfoView.setOnClickListener(v -> {
+ Intent fillInIntent = new Intent();
+ fillInIntent.putExtra(PrintService.EXTRA_CAN_SELECT_PRINTER, true);
+
+ try {
+ mPrinterForInfoIntent = printer;
+ startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
+ INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+ } catch (SendIntentException e) {
+ mPrinterForInfoIntent = null;
+ Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
}
});
} else {
diff --git a/packages/PrintSpooler/tests/Android.mk b/packages/PrintSpooler/tests/Android.mk
new file mode 100644
index 0000000..83e00ce
--- /dev/null
+++ b/packages/PrintSpooler/tests/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/PrintSpooler/tests/outofprocess/Android.mk b/packages/PrintSpooler/tests/outofprocess/Android.mk
new file mode 100644
index 0000000..d1d0ee4
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/Android.mk
@@ -0,0 +1,28 @@
+# 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_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
+
+LOCAL_PACKAGE_NAME := PrintSpoolerOutOfProcessTests
+
+include $(BUILD_PACKAGE)
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml
new file mode 100644
index 0000000..4a05f6f
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?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.printspooler.outofprocess.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name=".PrintTestActivity"/>
+
+ <service
+ android:name=".mockservice.MockPrintService"
+ android:permission="android.permission.BIND_PRINT_SERVICE">
+
+ <intent-filter>
+ <action android:name="android.printservice.PrintService" />
+ </intent-filter>
+ <meta-data
+ android:name="android.printservice"
+ android:resource="@xml/printservice">
+ </meta-data>
+ </service>
+
+ <activity
+ android:name=".mockservice.SettingsActivity"
+ android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+ android:exported="true">
+ </activity>
+
+ <activity
+ android:name=".mockservice.AddPrintersActivity"
+ android:exported="true">
+ </activity>
+
+ </application>
+
+ <!-- This runs in its own process, hence it instruments itself -->
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.printspooler.outofprocess.tests"
+ android:label="PrintSpooler Out of Process Test Cases">
+ </instrumentation>
+
+</manifest>
diff --git a/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml b/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml
new file mode 100644
index 0000000..9eecf45
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+
+<print-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:addPrintersActivity="com.android.printspooler.outofprocess.tests.mockservice.AddPrintersActivity" />
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/BasePrintTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/BasePrintTest.java
new file mode 100644
index 0000000..4de3570
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/BasePrintTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.printspooler.outofprocess.tests;
+
+import android.annotation.NonNull;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import com.android.printspooler.outofprocess.tests.mockservice.PrintServiceCallbacks;
+import com.android.printspooler.outofprocess.tests.mockservice.PrinterDiscoverySessionCallbacks;
+import com.android.printspooler.outofprocess.tests.mockservice.StubbablePrinterDiscoverySession;
+import android.printservice.CustomPrinterIconCallback;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.uiautomator.UiDevice;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.mockito.stubbing.Answer;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * This is the base class for print tests.
+ */
+abstract class BasePrintTest {
+ protected static final long OPERATION_TIMEOUT = 30000;
+ private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
+ private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
+
+ private android.print.PrintJob mPrintJob;
+
+ private static Instrumentation sInstrumentation;
+ private static UiDevice sUiDevice;
+
+ @Rule
+ public ActivityTestRule<PrintTestActivity> mActivityRule =
+ new ActivityTestRule<>(PrintTestActivity.class, false, true);
+
+ /**
+ * Return the UI device
+ *
+ * @return the UI device
+ */
+ public UiDevice getUiDevice() {
+ return sUiDevice;
+ }
+
+ protected static Instrumentation getInstrumentation() {
+ return sInstrumentation;
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ assumeTrue(sInstrumentation.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_PRINTING));
+
+ sUiDevice = UiDevice.getInstance(sInstrumentation);
+
+ // Make sure we start with a clean slate.
+ clearPrintSpoolerData();
+
+ // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+ // Dexmaker is used by mockito.
+ System.setProperty("dexmaker.dexcache", getInstrumentation()
+ .getTargetContext().getCacheDir().getPath());
+ }
+
+ @After
+ public void exitActivities() throws Exception {
+ // Exit print spooler
+ getUiDevice().pressBack();
+ getUiDevice().pressBack();
+ }
+
+ protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
+ final PrintAttributes attributes) {
+ // Initiate printing as if coming from the app.
+ getInstrumentation().runOnMainSync(() -> {
+ PrintManager printManager = (PrintManager) getActivity()
+ .getSystemService(Context.PRINT_SERVICE);
+ mPrintJob = printManager.print("Print job", adapter, attributes);
+ });
+
+ return mPrintJob;
+ }
+
+ protected PrintTestActivity getActivity() {
+ return mActivityRule.getActivity();
+ }
+
+ public static String runShellCommand(Instrumentation instrumentation, String cmd)
+ throws IOException {
+ ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd);
+ byte[] buf = new byte[512];
+ int bytesRead;
+ FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ StringBuilder stdout = new StringBuilder();
+ while ((bytesRead = fis.read(buf)) != -1) {
+ stdout.append(new String(buf, 0, bytesRead));
+ }
+ fis.close();
+ return stdout.toString();
+ }
+
+ protected static void clearPrintSpoolerData() throws Exception {
+ assertTrue("failed to clear print spooler data",
+ runShellCommand(getInstrumentation(), String.format(
+ "pm clear --user %d %s", CURRENT_USER_ID,
+ PrintManager.PRINT_SPOOLER_PACKAGE_NAME))
+ .contains(PM_CLEAR_SUCCESS_OUTPUT));
+ }
+
+ @SuppressWarnings("unchecked")
+ protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
+ Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
+ Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
+ Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
+ Answer<Void> onDestroy) {
+ PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
+
+ doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
+ when(callbacks.getSession()).thenCallRealMethod();
+
+ if (onStartPrinterDiscovery != null) {
+ doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
+ any(List.class));
+ }
+ if (onStopPrinterDiscovery != null) {
+ doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
+ }
+ if (onValidatePrinters != null) {
+ doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
+ any(List.class));
+ }
+ if (onStartPrinterStateTracking != null) {
+ doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
+ any(PrinterId.class));
+ }
+ if (onRequestCustomPrinterIcon != null) {
+ doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
+ any(PrinterId.class), any(CancellationSignal.class),
+ any(CustomPrinterIconCallback.class));
+ }
+ if (onStopPrinterStateTracking != null) {
+ doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
+ any(PrinterId.class));
+ }
+ if (onDestroy != null) {
+ doAnswer(onDestroy).when(callbacks).onDestroy();
+ }
+
+ return callbacks;
+ }
+
+ protected PrintServiceCallbacks createMockPrintServiceCallbacks(
+ Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
+ Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
+ final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
+
+ doCallRealMethod().when(service).setService(any(PrintService.class));
+ when(service.getService()).thenCallRealMethod();
+
+ if (onCreatePrinterDiscoverySessionCallbacks != null) {
+ doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
+ .onCreatePrinterDiscoverySessionCallbacks();
+ }
+ if (onPrintJobQueued != null) {
+ doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
+ }
+ if (onRequestCancelPrintJob != null) {
+ doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
+ any(PrintJob.class));
+ }
+
+ return service;
+ }
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/PrintTestActivity.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/PrintTestActivity.java
new file mode 100644
index 0000000..4905a0b
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/PrintTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * 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.printspooler.outofprocess.tests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class PrintTestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
new file mode 100644
index 0000000..184e559
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
@@ -0,0 +1,460 @@
+/*
+ * 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.printspooler.outofprocess.tests;
+
+import android.graphics.pdf.PdfDocument;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentInfo;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import com.android.printspooler.outofprocess.tests.mockservice.AddPrintersActivity;
+import com.android.printspooler.outofprocess.tests.mockservice.MockPrintService;
+import com.android.printspooler.outofprocess.tests.mockservice.PrinterDiscoverySessionCallbacks;
+import com.android.printspooler.outofprocess.tests.mockservice.StubbablePrinterDiscoverySession;
+import android.print.pdf.PrintedPdfDocument;
+import android.support.test.filters.LargeTest;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Tests for the basic printing workflows
+ */
+@RunWith(Parameterized.class)
+public class WorkflowTest extends BasePrintTest {
+ private static final String LOG_TAG = WorkflowTest.class.getSimpleName();
+
+ private static float sWindowAnimationScaleBefore;
+ private static float sTransitionAnimationScaleBefore;
+ private static float sAnimatiorDurationScaleBefore;
+
+ private PrintAttributes.MediaSize mFirst;
+ private boolean mSelectPrinter;
+ private PrintAttributes.MediaSize mSecond;
+
+ public WorkflowTest(PrintAttributes.MediaSize first, boolean selectPrinter,
+ PrintAttributes.MediaSize second) {
+ mFirst = first;
+ mSelectPrinter = selectPrinter;
+ mSecond = second;
+ }
+
+ interface InterruptableConsumer<T> {
+ void accept(T t) throws InterruptedException;
+ }
+
+ /**
+ * Execute {@code waiter} until {@code condition} is met.
+ *
+ * @param condition Conditions to wait for
+ * @param waiter Code to execute while waiting
+ */
+ private void waitWithTimeout(Supplier<Boolean> condition, InterruptableConsumer<Long> waiter)
+ throws TimeoutException, InterruptedException {
+ long startTime = System.currentTimeMillis();
+ while (condition.get()) {
+ long timeLeft = OPERATION_TIMEOUT - (System.currentTimeMillis() - startTime);
+ if (timeLeft < 0) {
+ throw new TimeoutException();
+ }
+
+ waiter.accept(timeLeft);
+ }
+ }
+
+ /**
+ * Executes a shell command using shell user identity, and return the standard output in
+ * string.
+ *
+ * @param cmd the command to run
+ *
+ * @return the standard output of the command
+ */
+ private static String runShellCommand(String cmd) throws IOException {
+ try (FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+ getInstrumentation().getUiAutomation().executeShellCommand(cmd))) {
+ byte[] buf = new byte[64];
+ int bytesRead;
+
+ StringBuilder stdout = new StringBuilder();
+ while ((bytesRead = is.read(buf)) != -1) {
+ stdout.append(new String(buf, 0, bytesRead));
+ }
+
+ return stdout.toString();
+ }
+ }
+
+ @BeforeClass
+ public static void disableAnimations() throws Exception {
+ try {
+ sWindowAnimationScaleBefore = Float.parseFloat(runShellCommand(
+ "settings get global window_animation_scale"));
+
+ runShellCommand("settings put global window_animation_scale 0");
+ } catch (NumberFormatException e) {
+ sWindowAnimationScaleBefore = Float.NaN;
+ }
+ try {
+ sTransitionAnimationScaleBefore = Float.parseFloat(runShellCommand(
+ "settings get global transition_animation_scale"));
+
+ runShellCommand("settings put global transition_animation_scale 0");
+ } catch (NumberFormatException e) {
+ sTransitionAnimationScaleBefore = Float.NaN;
+ }
+ try {
+ sAnimatiorDurationScaleBefore = Float.parseFloat(runShellCommand(
+ "settings get global animator_duration_scale"));
+
+ runShellCommand("settings put global animator_duration_scale 0");
+ } catch (NumberFormatException e) {
+ sAnimatiorDurationScaleBefore = Float.NaN;
+ }
+ }
+
+ @AfterClass
+ public static void enableAnimations() throws Exception {
+ if (sWindowAnimationScaleBefore != Float.NaN) {
+ runShellCommand(
+ "settings put global window_animation_scale " + sWindowAnimationScaleBefore);
+ }
+ if (sTransitionAnimationScaleBefore != Float.NaN) {
+ runShellCommand(
+ "settings put global transition_animation_scale " +
+ sTransitionAnimationScaleBefore);
+ }
+ if (sAnimatiorDurationScaleBefore != Float.NaN) {
+ runShellCommand(
+ "settings put global animator_duration_scale " + sAnimatiorDurationScaleBefore);
+ }
+ }
+
+ /** Add a printer with a given name and supported mediasize to a session */
+ private void addPrinter(StubbablePrinterDiscoverySession session,
+ String name, PrintAttributes.MediaSize mediaSize) {
+ PrinterId printerId = session.getService().generatePrinterId(name);
+ List<PrinterInfo> printers = new ArrayList<>(1);
+
+ PrinterCapabilitiesInfo.Builder builder =
+ new PrinterCapabilitiesInfo.Builder(printerId);
+
+ PrinterInfo printerInfo;
+ if (mediaSize != null) {
+ builder.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0))
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .addMediaSize(mediaSize, true)
+ .addResolution(new PrintAttributes.Resolution("300x300", "300x300", 300, 300),
+ true);
+
+ printerInfo = new PrinterInfo.Builder(printerId, name,
+ PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
+ } else {
+ printerInfo = (new PrinterInfo.Builder(printerId, name,
+ PrinterInfo.STATUS_IDLE)).build();
+ }
+
+ printers.add(printerInfo);
+ session.addPrinters(printers);
+ }
+
+ /** Find a certain element in the UI and click on it */
+ private void clickOn(UiSelector selector) throws UiObjectNotFoundException {
+ Log.i(LOG_TAG, "Click on " + selector);
+ UiObject view = getUiDevice().findObject(selector);
+ view.click();
+ getUiDevice().waitForIdle();
+ }
+
+ /** Find a certain text in the UI and click on it */
+ private void clickOnText(String text) throws UiObjectNotFoundException {
+ clickOn(new UiSelector().text(text));
+ }
+
+ /** Set the printer in the print activity */
+ private void setPrinter(String printerName) throws UiObjectNotFoundException {
+ clickOn(new UiSelector().resourceId("com.android.printspooler:id/destination_spinner"));
+
+ clickOnText(printerName);
+ }
+
+ /**
+ * Init mock print servic that returns a single printer by default.
+ *
+ * @param sessionRef Where to store the reference to the session once started
+ */
+ private void setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef,
+ ArrayList<String> trackedPrinters, PrintAttributes.MediaSize mediaSize) {
+ MockPrintService.setCallbacks(createMockPrintServiceCallbacks(
+ inv -> createMockPrinterDiscoverySessionCallbacks(inv2 -> {
+ synchronized (sessionRef) {
+ sessionRef[0] = ((PrinterDiscoverySessionCallbacks) inv2.getMock())
+ .getSession();
+
+ addPrinter(sessionRef[0], "1st printer", mediaSize);
+
+ sessionRef.notifyAll();
+ }
+ return null;
+ },
+ null, null, inv2 -> {
+ synchronized (trackedPrinters) {
+ trackedPrinters
+ .add(((PrinterId) inv2.getArguments()[0]).getLocalId());
+ trackedPrinters.notifyAll();
+ }
+ return null;
+ }, null, inv2 -> {
+ synchronized (trackedPrinters) {
+ trackedPrinters
+ .remove(((PrinterId) inv2.getArguments()[0]).getLocalId());
+ trackedPrinters.notifyAll();
+ }
+ return null;
+ }, inv2 -> {
+ synchronized (sessionRef) {
+ sessionRef[0] = null;
+ sessionRef.notifyAll();
+ }
+ return null;
+ }
+ ), null, null));
+ }
+
+ /**
+ * Start print operation that just prints a single empty page
+ *
+ * @param printAttributesRef Where to store the reference to the print attributes once started
+ */
+ private void print(PrintAttributes[] printAttributesRef) {
+ print(new PrintDocumentAdapter() {
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal, LayoutResultCallback callback,
+ Bundle extras) {
+ callback.onLayoutFinished((new PrintDocumentInfo.Builder("doc")).build(),
+ !newAttributes.equals(printAttributesRef[0]));
+
+ synchronized (printAttributesRef) {
+ printAttributesRef[0] = newAttributes;
+ printAttributesRef.notifyAll();
+ }
+ }
+
+ @Override
+ public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
+ CancellationSignal cancellationSignal, WriteResultCallback callback) {
+ try {
+ try {
+ PrintedPdfDocument document = new PrintedPdfDocument(getActivity(),
+ printAttributesRef[0]);
+ try {
+ PdfDocument.Page page = document.startPage(0);
+ document.finishPage(page);
+ try (FileOutputStream os = new FileOutputStream(
+ destination.getFileDescriptor())) {
+ document.writeTo(os);
+ os.flush();
+ }
+ } finally {
+ document.close();
+ }
+ } finally {
+ destination.close();
+ }
+
+ callback.onWriteFinished(pages);
+ } catch (IOException e) {
+ callback.onWriteFailed(e.getMessage());
+ }
+ }
+ }, null);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> getParameters() {
+ ArrayList<Object[]> tests = new ArrayList<>();
+
+ for (PrintAttributes.MediaSize first : new PrintAttributes.MediaSize[]{
+ PrintAttributes.MediaSize.ISO_A0, null}) {
+ for (Boolean selectPrinter : new Boolean[]{true, false}) {
+ for (PrintAttributes.MediaSize second : new PrintAttributes.MediaSize[]{
+ PrintAttributes.MediaSize.ISO_A1, null}) {
+ // If we do not use the second printer, no need to try various options
+ if (!selectPrinter && second == null) {
+ continue;
+ }
+ tests.add(new Object[]{first, selectPrinter, second});
+ }
+ }
+ }
+
+ return tests;
+ }
+
+ @Test
+ @LargeTest
+ public void addAndSelectPrinter() throws Exception {
+ final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1];
+ final PrintAttributes printAttributes[] = new PrintAttributes[1];
+ ArrayList<String> trackedPrinters = new ArrayList<>();
+
+ Log.i(LOG_TAG, "Running " + mFirst + " " + mSelectPrinter + " " + mSecond);
+
+ setMockPrintServiceCallbacks(session, trackedPrinters, mFirst);
+ print(printAttributes);
+
+ // We are now in the PrintActivity
+ Log.i(LOG_TAG, "Waiting for session");
+ synchronized (session) {
+ waitWithTimeout(() -> session[0] == null, session::wait);
+ }
+
+ setPrinter("1st printer");
+
+ Log.i(LOG_TAG, "Waiting for 1st printer to be tracked");
+ synchronized (trackedPrinters) {
+ waitWithTimeout(() -> !trackedPrinters.contains("1st printer"), trackedPrinters::wait);
+ }
+
+ if (mFirst != null) {
+ Log.i(LOG_TAG, "Waiting for print attributes to change");
+ synchronized (printAttributes) {
+ waitWithTimeout(
+ () -> printAttributes[0] == null ||
+ !printAttributes[0].getMediaSize().equals(
+ mFirst), printAttributes::wait);
+ }
+ } else {
+ Log.i(LOG_TAG, "Waiting for error message");
+ assertNotNull(getUiDevice().wait(Until.findObject(
+ By.text("This printer isn't available right now.")), OPERATION_TIMEOUT));
+ }
+
+ setPrinter("All printers\u2026");
+
+ // We are now in the SelectPrinterActivity
+ clickOnText("Add printer");
+
+ // We are now in the AddPrinterActivity
+ AddPrintersActivity.addObserver(
+ () -> addPrinter(session[0], "2nd printer", mSecond));
+
+ // This executes the observer registered above
+ clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName())
+ .resourceId("com.android.printspooler:id/title"));
+
+ getUiDevice().pressBack();
+ AddPrintersActivity.clearObservers();
+
+ if (mSelectPrinter) {
+ // We are now in the SelectPrinterActivity
+ clickOnText("2nd printer");
+ } else {
+ getUiDevice().pressBack();
+ }
+
+ // We are now in the PrintActivity
+ if (mSelectPrinter) {
+ if (mSecond != null) {
+ Log.i(LOG_TAG, "Waiting for print attributes to change");
+ synchronized (printAttributes) {
+ waitWithTimeout(
+ () -> printAttributes[0] == null ||
+ !printAttributes[0].getMediaSize().equals(
+ mSecond), printAttributes::wait);
+ }
+ } else {
+ Log.i(LOG_TAG, "Waiting for error message");
+ assertNotNull(getUiDevice().wait(Until.findObject(
+ By.text("This printer isn't available right now.")), OPERATION_TIMEOUT));
+ }
+
+ Log.i(LOG_TAG, "Waiting for 1st printer to be not tracked");
+ synchronized (trackedPrinters) {
+ waitWithTimeout(() -> trackedPrinters.contains("1st printer"),
+ trackedPrinters::wait);
+ }
+
+ Log.i(LOG_TAG, "Waiting for 2nd printer to be tracked");
+ synchronized (trackedPrinters) {
+ waitWithTimeout(() -> !trackedPrinters.contains("2nd printer"),
+ trackedPrinters::wait);
+ }
+ } else {
+ Thread.sleep(100);
+
+ if (mFirst != null) {
+ Log.i(LOG_TAG, "Waiting for print attributes to change");
+ synchronized (printAttributes) {
+ waitWithTimeout(
+ () -> printAttributes[0] == null ||
+ !printAttributes[0].getMediaSize().equals(
+ mFirst), printAttributes::wait);
+ }
+ } else {
+ Log.i(LOG_TAG, "Waiting for error message");
+ assertNotNull(getUiDevice().wait(Until.findObject(
+ By.text("This printer isn't available right now.")), OPERATION_TIMEOUT));
+ }
+
+ Log.i(LOG_TAG, "Waiting for 1st printer to be tracked");
+ synchronized (trackedPrinters) {
+ waitWithTimeout(() -> !trackedPrinters.contains("1st printer"),
+ trackedPrinters::wait);
+ }
+ }
+
+ getUiDevice().pressBack();
+
+ // We are back in the test activity
+ Log.i(LOG_TAG, "Waiting for session to end");
+ synchronized (session) {
+ waitWithTimeout(() -> session[0] != null, session::wait);
+ }
+ }
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/AddPrintersActivity.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/AddPrintersActivity.java
new file mode 100644
index 0000000..2ea4e7d
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/AddPrintersActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.printspooler.outofprocess.tests.mockservice;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+public class AddPrintersActivity extends Activity {
+ private static final ArrayList<Runnable> sObservers = new ArrayList<>();
+
+ public static void addObserver(@NonNull Runnable observer) {
+ synchronized (sObservers) {
+ sObservers.add(observer);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ synchronized (sObservers) {
+ for (Runnable sObserver : sObservers) {
+ sObserver.run();
+ }
+ }
+
+ finish();
+ }
+
+ public static void clearObservers() {
+ synchronized (sObservers) {
+ sObservers.clear();
+ }
+ }
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/MockPrintService.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/MockPrintService.java
new file mode 100644
index 0000000..3a23113
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/MockPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.printspooler.outofprocess.tests.mockservice;
+
+public class MockPrintService extends StubbablePrintService {
+
+ private static final Object sLock = new Object();
+
+ private static PrintServiceCallbacks sCallbacks;
+
+ public static void setCallbacks(PrintServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ @Override
+ protected PrintServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrintServiceCallbacks.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrintServiceCallbacks.java
new file mode 100644
index 0000000..07baa0f
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrintServiceCallbacks.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 com.android.printspooler.outofprocess.tests.mockservice;
+
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+
+public abstract class PrintServiceCallbacks {
+
+ private PrintService mService;
+
+ public PrintService getService() {
+ return mService;
+ }
+
+ public void setService(PrintService service) {
+ mService = service;
+ }
+
+ public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
+
+ public abstract void onRequestCancelPrintJob(PrintJob printJob);
+
+ public abstract void onPrintJobQueued(PrintJob printJob);
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrinterDiscoverySessionCallbacks.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrinterDiscoverySessionCallbacks.java
new file mode 100644
index 0000000..5c1260c
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrinterDiscoverySessionCallbacks.java
@@ -0,0 +1,51 @@
+/*
+ * 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.printspooler.outofprocess.tests.mockservice;
+
+import android.os.CancellationSignal;
+import android.print.PrinterId;
+import android.printservice.CustomPrinterIconCallback;
+
+import java.util.List;
+
+public abstract class PrinterDiscoverySessionCallbacks {
+
+ private StubbablePrinterDiscoverySession mSession;
+
+ public void setSession(StubbablePrinterDiscoverySession session) {
+ mSession = session;
+ }
+
+ public StubbablePrinterDiscoverySession getSession() {
+ return mSession;
+ }
+
+ public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
+
+ public abstract void onStopPrinterDiscovery();
+
+ public abstract void onValidatePrinters(List<PrinterId> printerIds);
+
+ public abstract void onStartPrinterStateTracking(PrinterId printerId);
+
+ public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
+ CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
+
+ public abstract void onStopPrinterStateTracking(PrinterId printerId);
+
+ public abstract void onDestroy();
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrintService.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrintService.java
new file mode 100644
index 0000000..be9d19b
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrintService.java
@@ -0,0 +1,52 @@
+/*
+ * 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.printspooler.outofprocess.tests.mockservice;
+
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+
+public abstract class StubbablePrintService extends PrintService {
+
+ @Override
+ public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ return new StubbablePrinterDiscoverySession(this,
+ getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
+ }
+ return null;
+ }
+
+ @Override
+ public void onRequestCancelPrintJob(PrintJob printJob) {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.onRequestCancelPrintJob(printJob);
+ }
+ }
+
+ @Override
+ public void onPrintJobQueued(PrintJob printJob) {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.onPrintJobQueued(printJob);
+ }
+ }
+
+ protected abstract PrintServiceCallbacks getCallbacks();
+}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrinterDiscoverySession.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrinterDiscoverySession.java
new file mode 100644
index 0000000..a828cd6
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrinterDiscoverySession.java
@@ -0,0 +1,95 @@
+/*
+ * 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.printspooler.outofprocess.tests.mockservice;
+
+import android.os.CancellationSignal;
+import android.print.PrinterId;
+import android.printservice.CustomPrinterIconCallback;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
+ private final PrintService mService;
+ private final PrinterDiscoverySessionCallbacks mCallbacks;
+
+ public StubbablePrinterDiscoverySession(PrintService service,
+ PrinterDiscoverySessionCallbacks callbacks) {
+ mService = service;
+ mCallbacks = callbacks;
+ if (mCallbacks != null) {
+ mCallbacks.setSession(this);
+ }
+ }
+
+ public PrintService getService() {
+ return mService;
+ }
+
+ @Override
+ public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
+ if (mCallbacks != null) {
+ mCallbacks.onStartPrinterDiscovery(priorityList);
+ }
+ }
+
+ @Override
+ public void onStopPrinterDiscovery() {
+ if (mCallbacks != null) {
+ mCallbacks.onStopPrinterDiscovery();
+ }
+ }
+
+ @Override
+ public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
+ if (mCallbacks != null) {
+ mCallbacks.onValidatePrinters(printerIds);
+ }
+ }
+
+ @Override
+ public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
+ if (mCallbacks != null) {
+ mCallbacks.onStartPrinterStateTracking(printerId);
+ }
+ }
+
+ @Override
+ public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull CustomPrinterIconCallback callback) {
+ if (mCallbacks != null) {
+ mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
+ }
+ }
+
+ @Override
+ public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
+ if (mCallbacks != null) {
+ mCallbacks.onStopPrinterStateTracking(printerId);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mCallbacks != null) {
+ mCallbacks.onDestroy();
+ }
+ }
+}
diff --git a/packages/SettingsLib/res/layout/restricted_switch_preference.xml b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
index 89dc10b..bb1d906 100644
--- a/packages/SettingsLib/res/layout/restricted_switch_preference.xml
+++ b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
@@ -20,7 +20,7 @@
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/activatedBackgroundIndicator"
+ android:background="?android:attr/selectableItemBackground"
android:clipToPadding="false">
<LinearLayout
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 81af941..295248c 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi-verbinding het misluk"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Stawingsprobleem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nie binne ontvangs nie"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Sal nie outomaties koppel nie"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Geen internettoegang nie"</string>
<string name="saved_network" msgid="4352716707126620811">"Gestoor deur <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Gekoppel via Wi-Fi-assistent"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Gekoppel via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Wys snitgrense, kantlyne, ens."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Dwing RTL-uitlegrigting"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Dwing skermuitlegrigting na RTL vir alle locales"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Wys CPU-verbruik"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Skermlaag wys huidige CPU-gebruik"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forseer GPU-lewering"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Dwing gebruik van GPU vir 2D-tekening"</string>
<string name="force_msaa" msgid="7920323238677284387">"Dwing 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 792c434..bb50087 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"የWiFi ግንኙነት መሰናከል"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"የማረጋገጫ ችግር"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"በክልል ውስጥ የለም"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"በራስ-ሰር አይገናኝም"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ምንም የበይነመረብ መዳረሻ ያለም"</string>
<string name="saved_network" msgid="4352716707126620811">"የተቀመጠው በ<xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"በWi‑Fi ረዳት አማካኝነት ተገናኝቷል"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"በ%1$s በኩል መገናኘት"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"የቅንጥብ ገደቦች፣ ጠርዞች፣ ወዘተ አሳይ"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"የቀኝ-ወደ-ግራ አቀማመጥ አቅጣጫ አስገድድ"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"ለሁሉም አካባቢዎች የማያ ገጽ አቀማመጥ ከቀኝ-ወደ-ግራ እንዲሆን አስገድድ"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"የCPU አጠቃቀም አሳይ"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"የማያ ተደራቢ የአሁኑን የCPU አጠቃቀም እያሳየ ነው።"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU ምላሽ መስጠትን አስገድድ"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"ለ2-ልኬት መሳል የGPU ስራ አስገድድ"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA አስገድድ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index db9afee..829123c 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"أخفق اتصال WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"حدثت مشكلة في المصادقة"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"ليست في النطاق"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"لن يتم الاتصال تلقائيًا"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"لا يتوفر اتصال بالإنترنت"</string>
<string name="saved_network" msgid="4352716707126620811">"تم الحفظ بواسطة <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"تم التوصيل عبر مساعد Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"تم الاتصال عبر %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"عرض حدود وهوامش المقطع وما إلى ذلك."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"فرض اتجاه التنسيق ليكون من اليمين إلى اليسار"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"فرض اتجاه تنسيق الشاشة ليكون من اليمين إلى اليسار لجميع اللغات"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"عرض استخدام CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"عرض تراكب الشاشة لاستخدام CPU الحالي"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"فرض عرض رسومات GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"فرض استخدام وحدة معالجة الرسومات للرسم ثنائي الأبعاد"</string>
<string name="force_msaa" msgid="7920323238677284387">"فرض 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-az-rAZ/strings.xml b/packages/SettingsLib/res/values-az-rAZ/strings.xml
index 130cd75..483770f 100644
--- a/packages/SettingsLib/res/values-az-rAZ/strings.xml
+++ b/packages/SettingsLib/res/values-az-rAZ/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi Bağlantı Uğursuzluğu"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentifikasiya problemi"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Diapazonda deyil"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Avtomatik qoşulmayacaq"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"İnternet girişi yoxdur"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> tərəfindən saxlandı"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi köməkçisi vasitəsilə qoşulub"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s vasitəsilə qoşuludur"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Klip sərhədləri, boşluqları və s. göstər"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL düzən istiqamətinə məcbur edin"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Ekran düzən istiqamətini RTL üzərinə bütün yerli variantlar üçün məcbur edin"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU istifadəsini göstər"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Ekran örtüşməsi cari CPU istifadəsini göstərir"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU renderə məcbur edin"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d rəsm üçün GPU məcburi istifadə"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA məcbur edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 5d7e852..5498580a 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi veza je otkazala"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problem sa potvrdom autentičnosti"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nije u opsegu"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Automatsko povezivanje nije uspelo"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nema pristupa internetu"</string>
<string name="saved_network" msgid="4352716707126620811">"Sačuvao/la je <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Povezano preko Wi‑Fi pomoćnika"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Veza je uspostavljena preko pristupne tačke %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Prikaži granice klipa, margine itd."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Nametni smer rasporeda zdesna nalevo"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Nametni smer rasporeda ekrana zdesna nalevo za sve lokalitete"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Prik. upotrebu procesora"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Postav. element sa trenutnom upotrebom procesora"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Prinudni prikaz pom. GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Prinudno koristi GPU za 2D crtanje"</string>
<string name="force_msaa" msgid="7920323238677284387">"Nametni 4x MSAA"</string>
@@ -285,7 +281,7 @@
<string name="enable_webview_multiprocess_desc" msgid="2485604010404197724">"Pokrećite WebView prikazivače zasebno"</string>
<string name="select_webview_provider_title" msgid="4628592979751918907">"Primena WebView-a"</string>
<string name="select_webview_provider_dialog_title" msgid="4370551378720004872">"Podesite primenu WebView-a"</string>
- <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"Ovaj izbor više nije važeći. Pokušajte ponovo."</string>
+ <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"Ovaj izbor više nije važeći. Probajte ponovo."</string>
<string name="convert_to_file_encryption" msgid="3060156730651061223">"Konvertuj u šifrovanje datoteka"</string>
<string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"Konvertuj..."</string>
<string name="convert_to_file_encryption_done" msgid="7859766358000523953">"Već se koristi šifrovanje datoteka"</string>
diff --git a/packages/SettingsLib/res/values-be-rBY/strings.xml b/packages/SettingsLib/res/values-be-rBY/strings.xml
index a8428f2..f233db9 100644
--- a/packages/SettingsLib/res/values-be-rBY/strings.xml
+++ b/packages/SettingsLib/res/values-be-rBY/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Збой падлучэння Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Праблема аўтэнтыфікацыі"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Не ў зоне дасягальнасці"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Не будзе аўтаматычна падключацца"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Няма доступу да інтэрнэту"</string>
<string name="saved_network" msgid="4352716707126620811">"Хто захаваў: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Падлучана праз памочніка Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Падлучана праз %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Паказаць межы кліпу, палі і г. д."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Прымусовая раскладка справа налева"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Прымусовая раскладка экрана справа налева для ўсіх рэгіянальных налад"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Паказаць выкарыстанне ЦП"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Наклад на экран з бягучым выкарыстаннем працэсара"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Прымусовае адлюстраванне GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Прымусовае выкарыстанне GPU для 2-мерных чарцяжоў"</string>
<string name="force_msaa" msgid="7920323238677284387">"Прымусовае выкананне 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index b356766..cd77792 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Неуспешна връзка с Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Проблем при удостоверяването"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Извън обхват"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Няма да се свърже автоматично"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Няма достъп до интернет"</string>
<string name="saved_network" msgid="4352716707126620811">"Запазено от <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Установена е връзка чрез помощника за Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Установена е връзка през „%1$s“"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Показв. на границите на изрязване, полетата и др."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Принуд. оформл. отдясно наляво"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Принуд. оформл. на екрана отдясно наляво за вс. локали"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Употреба на процесора"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Наслагване на екрана показва употр. на процесора"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Принудително изобразяване"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Принуд. използв. на GPU за двуизмерно начертаване"</string>
<string name="force_msaa" msgid="7920323238677284387">"Задаване на 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-bn-rBD/strings.xml b/packages/SettingsLib/res/values-bn-rBD/strings.xml
index 4089562..a011fcf 100644
--- a/packages/SettingsLib/res/values-bn-rBD/strings.xml
+++ b/packages/SettingsLib/res/values-bn-rBD/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi সংযোগের ব্যর্থতা"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"প্রমাণীকরণ সমস্যা"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"পরিসরের মধ্যে নয়"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"স্বয়ংক্রিয়ভাবে সংযোগ করবে না"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"কোনো ইন্টারনেট অ্যাক্সেস নেই"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> দ্বারা সংরক্ষিত"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"ওয়াই-ফাই সহায়ক-এর মাধ্যমে সংযুক্ত হয়েছে"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s মাধ্যমে সংযুক্ত হয়েছে"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ক্লিপ বাউন্ড, মার্জিন ইত্যাদি দেখান"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL লেআউট দিকনির্দেশ জোর দিন"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"সমস্ত স্থানের জন্য RTL এ স্ক্রীন লেআউট দিকনির্দেশে জোর দেয়"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU এর ব্যবহার দেখান"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"স্ক্রীন ওভারলে বর্তমান CPU ব্যবহার দেখাচ্ছে"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"জোর করে GPU রেন্ডারিং করুন"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2D অঙ্কনের জন্য GPU-এর ব্যবহারে জোর দিন"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA এ জোর দিন"</string>
diff --git a/packages/SettingsLib/res/values-bs-rBA/strings.xml b/packages/SettingsLib/res/values-bs-rBA/strings.xml
index 5115bbd..1df5ec9 100644
--- a/packages/SettingsLib/res/values-bs-rBA/strings.xml
+++ b/packages/SettingsLib/res/values-bs-rBA/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Greška pri povezivanju na Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problem pri provjeri vjerodostojnosti."</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nije u dometu"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Neće se automatski povezati"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nema pristupa internetu"</string>
<string name="saved_network" msgid="4352716707126620811">"Sačuvao <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Povezani preko Wi-Fi pomoćnika"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Povezani preko %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Prikaži granice isječka, margine itd."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Prisilno postavi raspored s desna u lijevo"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Prisilno postavi raspored ekrana s desna u lijevo za sve regije"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Prikaži korištenje CPU-a"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Trenutno korištenje CPU-a prikazuje se u nadsloju preko ekrana"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Prisili GPU iscrtavanje"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Prisilno koristite GPU za 2d crtanje"</string>
<string name="force_msaa" msgid="7920323238677284387">"Prinudno primjeni 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index abc9390..92e9005 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Error de connexió Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema d\'autenticació"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fora de l\'abast"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"No es connectarà automàticament"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"No hi ha accés a Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Desat per <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connectat mitjançant l\'assistent de Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connectada mitjançant %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostra els límits de clips, els marges, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Força direcció dreta-esquerra"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Força direcció de pantalla dreta-esquerra en totes les llengües"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostra l\'ús de la CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Superposa l\'ús de la CPU a la pantalla"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Força acceleració GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Força l\'ús de GPU per a dibuixos en 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Força MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index edc53ed..8ec531c 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Selhání připojení Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problém s ověřením"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Mimo dosah"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Připojení nebude automaticky navázáno"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nebyl zjištěn žádný přístup k internetu"</string>
<string name="saved_network" msgid="4352716707126620811">"Uloženo uživatelem <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Připojeno pomocí asistenta připojení Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Připojeno prostřednictvím %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Zobrazit u výstřižku ohraničení, okraje atd."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Vynutit rozvržení zprava doleva"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Vynutit ve všech jazycích rozvržení obrazovky zprava doleva"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Zobrazit využití CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Překryvná vrstva s aktuálním využitím procesoru"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Vykreslování pomocí GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Vynutit použití GPU pro 2D vykreslování"</string>
<string name="force_msaa" msgid="7920323238677284387">"Vynutit 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 5714901..a65c58f 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi-forbindelsesfejl"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problem med godkendelse"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ikke inden for rækkevidde"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Der oprettes ikke automatisk forbindelse"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ingen internetadgang"</string>
<string name="saved_network" msgid="4352716707126620811">"Gemt af <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Forbindelse via Wi-Fi-assistent"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Tilsluttet via %1$s"</string>
@@ -154,7 +152,7 @@
<string name="clear_adb_keys" msgid="4038889221503122743">"Tilbagekald tilladelser for USB-fejlfinding"</string>
<string name="bugreport_in_power" msgid="7923901846375587241">"Genvej til fejlrapporting"</string>
<string name="bugreport_in_power_summary" msgid="1778455732762984579">"Vis en knap til oprettelse af fejlrapporter i menu for slukknap"</string>
- <string name="keep_screen_on" msgid="1146389631208760344">"Undgå dvale"</string>
+ <string name="keep_screen_on" msgid="1146389631208760344">"Lås ikke"</string>
<string name="keep_screen_on_summary" msgid="2173114350754293009">"Skærmen går ikke i dvale under opladning"</string>
<string name="bt_hci_snoop_log" msgid="3340699311158865670">"Aktivér Bluetooth HCI snoop log"</string>
<string name="bt_hci_snoop_log_summary" msgid="730247028210113851">"Gem alle Bluetooth HCI-pakker i en fil"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Vis grænser for klip, margener osv."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Tving læsning mod venstre"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Tving til højre mod venstre-layout for alle sprog"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Vis CPU-forbrug"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Skærmoverlejring viser det aktuelle CPU-forbrug"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Tving gengivelse af GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Gennemtving brug af GPU til 2D-tegning"</string>
<string name="force_msaa" msgid="7920323238677284387">"Tving 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 406523e..923eca1 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WLAN-Verbindungsfehler"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Authentifizierungsproblem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nicht in Reichweite"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Kein automatischer Verbindungsaufbau"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Kein Internetzugriff"</string>
<string name="saved_network" msgid="4352716707126620811">"Gespeichert von <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Über WLAN-Assistenten verbunden"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Über %1$s verbunden"</string>
@@ -41,7 +39,7 @@
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Verbindung wird getrennt..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Verbindung wird hergestellt..."</string>
<string name="bluetooth_connected" msgid="6038755206916626419">"Verbunden"</string>
- <string name="bluetooth_pairing" msgid="1426882272690346242">"Pairing läuft…"</string>
+ <string name="bluetooth_pairing" msgid="1426882272690346242">"Kopplung läuft…"</string>
<string name="bluetooth_connected_no_headset" msgid="2866994875046035609">"Verbunden (kein Telefon)"</string>
<string name="bluetooth_connected_no_a2dp" msgid="4576188601581440337">"Verbunden (außer Audiomedien)"</string>
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Verbunden (ohne Nachrichtenzugriff)"</string>
@@ -72,12 +70,12 @@
<string name="bluetooth_headset_profile_summary_use_for" msgid="8705753622443862627">"Für Audiosystem des Telefons verwenden"</string>
<string name="bluetooth_opp_profile_summary_use_for" msgid="1255674547144769756">"Für Dateiübertragung verwenden"</string>
<string name="bluetooth_hid_profile_summary_use_for" msgid="232727040453645139">"Für Eingabe verwenden"</string>
- <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"Pairing durchführen"</string>
- <string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"Pairing durchführen"</string>
+ <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"Koppeln"</string>
+ <string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"KOPPELN"</string>
<string name="bluetooth_pairing_decline" msgid="4185420413578948140">"Abbrechen"</string>
- <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Über das Pairing kann auf deine Kontakte und auf deinen Anrufverlauf zugegriffen werden, wenn eine Verbindung besteht."</string>
- <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Pairing mit <xliff:g id="DEVICE_NAME">%1$s</xliff:g> war nicht möglich."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Pairing mit <xliff:g id="DEVICE_NAME">%1$s</xliff:g> war nicht möglich, weil die eingegebene PIN oder der Zugangscode falsch ist."</string>
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Über die Kopplung kann auf deine Kontakte und auf deinen Anrufverlauf zugegriffen werden, wenn eine Verbindung besteht."</string>
+ <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Kopplung mit <xliff:g id="DEVICE_NAME">%1$s</xliff:g> war nicht möglich."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Kopplung mit <xliff:g id="DEVICE_NAME">%1$s</xliff:g> war nicht möglich, weil die eingegebene PIN oder der Zugangscode falsch ist."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Kommunikation mit <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ist nicht möglich."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Verbindung wurde von <xliff:g id="DEVICE_NAME">%1$s</xliff:g> abgelehnt."</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"WLAN: aus"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Clip-Begrenzungen, Ränder usw. anzeigen"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL-Layout erzwingen"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"RTL-Bildschirmlayout für alle Sprachen erzwingen"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU-Auslastung anzeigen"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Bildschirm-Overlay mit aktueller CPU-Auslastung"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU-Rendering erzwingen"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Einsatz von GPU für 2D-Zeichnung erzwingen"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA erzwingen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 117b9fe..3c0247d 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Αποτυχία σύνδεσης Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Πρόβλημα ελέγχου ταυτότητας"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Εκτός εμβέλειας"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Δεν θα συνδεθεί αυτόματα"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Δεν υπάρχει πρόσβαση στο διαδίκτυο"</string>
<string name="saved_network" msgid="4352716707126620811">"Αποθηκεύτηκε από <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Σύνδεση μέσω βοηθού Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Συνδέθηκε μέσω %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Εμφάνιση ορίων κλιπ, περιθωρίων, κλπ."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Επιβολή κατ. διάταξης RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Επιβολή διάταξης οθόν. RTL για όλες τις τοπ. ρυθμ."</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Προβολή χρήσης CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Επικάλυψη οθόνης για προβολή τρέχουσας χρήσης CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Αναγκαστική απόδοση GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Αναγκαστική χρήση του GPU για σχέδιο 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Αναγκαστικά 4x MSAA"</string>
@@ -290,8 +286,8 @@
<string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"Μετατροπή…"</string>
<string name="convert_to_file_encryption_done" msgid="7859766358000523953">"Με κρυπτογράφηση αρχείου"</string>
<string name="title_convert_fbe" msgid="1263622876196444453">"Μετατροπή σε κρυπτογράφηση βάσει αρχείου…"</string>
- <string name="convert_to_fbe_warning" msgid="6139067817148865527">"Μετατροπή τμήματος δεδομένων σε κρυπτογράφηση βάσει αρχείου.\n !!Προσοχή!! Με αυτήν την ενέργεια, θα διαγραφούν όλα τα δεδομένα σας.\n Αυτή η λειτουργία βρίσκεται σε δοκιμαστικό στάδιο alpha και ενδέχεται να μην λειτουργεί σωστά.\n Πατήστε \"Εκκαθάριση και μετατροπή…\" για να συνεχίσετε."</string>
- <string name="button_convert_fbe" msgid="5152671181309826405">"Εκκαθάριση και μετατροπή…"</string>
+ <string name="convert_to_fbe_warning" msgid="6139067817148865527">"Μετατροπή τμήματος δεδομένων σε κρυπτογράφηση βάσει αρχείου.\n !!Προσοχή!! Με αυτήν την ενέργεια, θα διαγραφούν όλα τα δεδομένα σας.\n Αυτή η λειτουργία βρίσκεται σε δοκιμαστικό στάδιο alpha και ενδέχεται να μην λειτουργεί σωστά.\n Πατήστε \"Διαγραφή και μετατροπή…\" για να συνεχίσετε."</string>
+ <string name="button_convert_fbe" msgid="5152671181309826405">"Διαγραφή και μετατροπή…"</string>
<string name="picture_color_mode" msgid="4560755008730283695">"Λειτουργία χρώματος εικόνας"</string>
<string name="picture_color_mode_desc" msgid="1141891467675548590">"Χρήση sRGB"</string>
<string name="daltonizer_mode_disabled" msgid="7482661936053801862">"Απενεργοποιημένο"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 35ec15b..8ed7444 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi Connection Failure"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Authentication problem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Not in range"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Won\'t automatically connect"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"No Internet access"</string>
<string name="saved_network" msgid="4352716707126620811">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connected via Wi‑Fi assistant"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connected via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Show clip bounds, margins, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Force RTL layout direction"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Force screen layout direction to RTL for all locales"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Show CPU usage"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Screen overlay showing current CPU usage"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Force GPU rendering"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Force use of GPU for 2D drawing"</string>
<string name="force_msaa" msgid="7920323238677284387">"Force 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 35ec15b..8ed7444 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi Connection Failure"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Authentication problem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Not in range"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Won\'t automatically connect"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"No Internet access"</string>
<string name="saved_network" msgid="4352716707126620811">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connected via Wi‑Fi assistant"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connected via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Show clip bounds, margins, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Force RTL layout direction"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Force screen layout direction to RTL for all locales"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Show CPU usage"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Screen overlay showing current CPU usage"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Force GPU rendering"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Force use of GPU for 2D drawing"</string>
<string name="force_msaa" msgid="7920323238677284387">"Force 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 35ec15b..8ed7444 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi Connection Failure"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Authentication problem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Not in range"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Won\'t automatically connect"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"No Internet access"</string>
<string name="saved_network" msgid="4352716707126620811">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connected via Wi‑Fi assistant"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connected via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Show clip bounds, margins, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Force RTL layout direction"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Force screen layout direction to RTL for all locales"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Show CPU usage"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Screen overlay showing current CPU usage"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Force GPU rendering"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Force use of GPU for 2D drawing"</string>
<string name="force_msaa" msgid="7920323238677284387">"Force 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 2874e61..cdfbc01 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Error de conexión Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticación"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fuera de alcance"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"No se conectará automáticamente"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"No se detectó acceso a Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Guardadas por <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Conexión por asistente de Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conexión a través de %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostrar límites de recortes, márgenes, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forzar diseño der. a izq."</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forzar diseño pantalla der.>izq., cualquier idioma"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostrar el uso de CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Mostrar superposición en pantalla con uso actual de la CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forzar representación GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forzar uso de GPU para dibujar en 2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forzar MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 7d0097a..2a21d22 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Error de conexión Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Error de autenticación"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fuera de rango"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"No se establecerá conexión automáticamente"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"No se ha detectado acceso a Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Guardadas por <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Conectado a través de asistente Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado a través de %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostrar límites de vídeo, márgenes, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forzar dirección diseño RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forzar dirección (RTL) para todas configuraciones"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostrar uso de la CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Suporponer el uso de la CPU en la pantalla"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forzar aceleración GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forzar uso de GPU para dibujos en 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forzar MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-et-rEE/strings.xml b/packages/SettingsLib/res/values-et-rEE/strings.xml
index 97fe73f..644bf59 100644
--- a/packages/SettingsLib/res/values-et-rEE/strings.xml
+++ b/packages/SettingsLib/res/values-et-rEE/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi-ühenduse viga"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentimise probleem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Pole vahemikus"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Automaatselt ei ühendata"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Interneti-ühendus puudub"</string>
<string name="saved_network" msgid="4352716707126620811">"Salvestas: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Ühendatud WiFi-abi kaudu"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Ühendatud üksuse %1$s kaudu"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Kuva klipi piirid, veerised jms"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Paremalt vasakule paig."</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Määra lokaatides ekraanipaig. paremalt vasakule"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU-kasutuse kuvamine"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Praegust CPU-kasutust kuvav ekraani ülekate"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Jõusta GPU renderdamine"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Jõusta GPU kasutam. kahemõõtmeliste jooniste puhul"</string>
<string name="force_msaa" msgid="7920323238677284387">"Jõusta 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-eu-rES/strings.xml b/packages/SettingsLib/res/values-eu-rES/strings.xml
index 9234f24..2217611 100644
--- a/packages/SettingsLib/res/values-eu-rES/strings.xml
+++ b/packages/SettingsLib/res/values-eu-rES/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Ezin izan da konektatu Wi-Fi sarera"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentifikazio-arazoa"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Urrunegi"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Ez da konektatuko automatikoki"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ezin da konektatu Internetera"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> aplikazioak gorde du"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi laguntzailearen bidez konektatuta"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s bidez konektatuta"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Erakutsi kliparen mugak, marjinak, etab."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Behartu eskuin-ezker norabidea."</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Behartu pantaila-diseinuaren norabidea eskuin-ezker izatera eskualdeko ezarpen guztiekin."</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Erakutsi PUZ erabilera"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"PUZ erabilera erakusten duen pantaila-gainjartzea"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Behartu GPU errendatzea"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Behartu GPUa erabiltzera 2 dimentsioko marrazkietan."</string>
<string name="force_msaa" msgid="7920323238677284387">"Behartu 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index bb2e166..88ed7fa 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"اتصال Wi-Fi برقرار نشد"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"مشکل احراز هویت"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"در محدوده نیست"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"اتصال بهصورت خودکار انجام نمیشود"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"دسترسی به اینترنت وجود ندارد"</string>
<string name="saved_network" msgid="4352716707126620811">"ذخیرهشده توسط <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"متصل شده از طریق دستیار Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"متصل از طریق %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"نمایش مرزها، حاشیهها و ویژگیهای دیگر کلیپ."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"اجباری کردن چیدمان RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"اجباری کردن چیدمان RTL صفحه برای همه زبانها"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"نمایش میزان استفاده از CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"همپوشانی صفحهنمایش میزان استفاده از CPU فعلی"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"پردازش اجباری GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"استفاده اجباری از GPU برای طراحی دوم"</string>
<string name="force_msaa" msgid="7920323238677284387">"اجبار 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index c04c970..d26fc05 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi-yhteysvirhe"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Todennusvirhe"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ei kantoalueella"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Yhteyttä ei muodosteta automaattisesti"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ei internetyhteyttä"</string>
<string name="saved_network" msgid="4352716707126620811">"Tallentaja: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Yhteys muodostettu Wi‑Fi-apurin kautta"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Yhdistetty seuraavan kautta: %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Näytä leikkeiden rajat, marginaalit jne."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Pakota RTL-ulkoasun suunta"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Pakota kaikkien kielten näytön ulkoasun suunnaksi RTL"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Näytä suorittimen käyttö"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Näytön peittokuva näyttää nykyisen suoritinkäytön"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Pakota GPU-hahmonnus"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Käytä GPU:ta 2d-piirtämiseen"</string>
<string name="force_msaa" msgid="7920323238677284387">"Pakota 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 1b34904..caad889 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Échec de connexion Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problème d\'authentification"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Hors de portée"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Reconnexion automatique impossible"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Aucun accès à Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Enregistrés par <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connecté à l\'aide de l\'assistant Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connecté par %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Afficher les limites, les marges de clip, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forcer orient. : g. à d."</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forcer l\'orientation: g. à droite (toutes langues)"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Afficher mém. CPU utilisée"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Superposition écran indiquant mémoire CPU utilisée"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forcer le rendu GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forcer l\'utilisation du GPU pour le dessin 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forcer MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 07398f3..91bbaf0 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Échec de la connexion Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problème d\'authentification."</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Hors de portée"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Reconnexion automatique impossible"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Aucun accès à Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Enregistré par <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connecté via l\'assistant Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connecté via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Afficher les limites de coupe, les marges, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forcer droite à gauche"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forcer orient. droite à gauche pour toutes langues"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Afficher mém. CPU utilisée"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Superposition écran indiquant mémoire CPU utilisée"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forcer le rendu GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forcer l\'utilisation du GPU pour le dessin 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forcer MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-gl-rES/strings.xml b/packages/SettingsLib/res/values-gl-rES/strings.xml
index 5207a73..b717382 100644
--- a/packages/SettingsLib/res/values-gl-rES/strings.xml
+++ b/packages/SettingsLib/res/values-gl-rES/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Erro na conexión wifi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticación"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Non está dentro da zona de cobertura"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Non se conectará automaticamente"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Non hai acceso a Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Redes gardadas por <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Conectado ao asistente de wifi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado a través de %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostra os límites dos clips, as marxes, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forzar dirección do deseño RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forza a dirección de pantalla a RTL (dereita a esquerda) para todas as configuración rexionais"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostrar uso da CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Superpoñer o uso da CPU na pantalla"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forzar procesamento GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forzar o uso de GPU para o debuxo en 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forzar MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-gu-rIN/strings.xml b/packages/SettingsLib/res/values-gu-rIN/strings.xml
index 3d0a02b..5605189 100644
--- a/packages/SettingsLib/res/values-gu-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-gu-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi કનેક્શન નિષ્ફળ"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"પ્રમાણીકરણ સમસ્યા"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"રેન્જમાં નથી"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"આપમેળે કનેક્ટ કરશે નહીં"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"કોઈ ઇન્ટરનેટ ઍક્સેસ નથી"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા સચવાયું"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi-Fi સહાયક દ્વારા કનેક્ટ થયું"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s દ્વારા કનેક્ટ થયેલ"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ક્લિપ બાઉન્ડ્સ, હાંસિયાં વગેરે બતાવો."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL લેઆઉટ દિશા નિર્દેશની ફરજ પાડો"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"તમામ લૉકેલ્સ માટે સ્ક્રીન લેઆઉટ દિશા નિર્દેશને RTL ની ફરજ પાડો"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU સંગ્રહ બતાવો"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"વર્તમાન CPU વપરાશ દર્શાવતું સ્ક્રીન ઓવરલે"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU રેન્ડરિંગની ફરજ પાડો"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2જા રેખાંકન માટે GPU ના ઉપયોગની ફરજ પાડો"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA ને ફરજ પાડો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 329a670..d579ec4 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"वाईफ़ाई कनेक्शन विफलता"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"प्रमाणीकरण समस्या"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"रेंज में नहीं"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"अपने आप कनेक्ट नहीं होगा"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"कोई इंटरनेट एक्सेस नहीं"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> के द्वारा सहेजा गया"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"वाई-फ़ाई सहायक के द्वारा कनेक्ट है"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s के द्वारा उपलब्ध"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"क्लिप सीमाएं, मार्जिन, आदि दिखाएं."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL लेआउट दिशा लागू करें"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"सभी भाषाओं के लिए स्क्रीन लेआउट दिशा को RTL रखें"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU उपयोग दिखाएं"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"स्क्रीन ओवरले वर्तमान CPU उपयोग को दिखा रहा है"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"बलपूर्वक GPU रेंडर करें"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d ड्रॉइंग के लिए GPU का बलपूर्वक उपयोग करें"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA को बाध्य करें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 89b0653..94229b1 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Povezivanje s Wi-Fi-jem nije uspjelo"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problem u autentifikaciji"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nije u rasponu"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Neće se povezati automatski"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nema pristupa internetu"</string>
<string name="saved_network" msgid="4352716707126620811">"Spremljeno: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Povezani putem pomoćnika za Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Povezano putem %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Prikazuju se obrubi, margine itd. isječaka."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Nametni zdesna ulijevo"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Nametni smjer zdesna ulijevo za sve zemlje/jezike"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Prikaži upotrebu procesora"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Na zaslonu se prikazuje iskorištenost procesora."</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Nametni GPU renderiranje"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Nametni upotrebu GPU-a za 2D crteže"</string>
<string name="force_msaa" msgid="7920323238677284387">"Nametni 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index c33943a..34f06e5 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi-kapcsolati hiba"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Azonosítási probléma"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Hatókörön kívül"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Nem csatlakozik automatikusan"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nincs internet-hozzáférés"</string>
<string name="saved_network" msgid="4352716707126620811">"Mentette: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Csatlakozva Wi‑Fi-segéddel"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Csatlakozva a következőn keresztül: %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Kliphatárok, margók stb. megjelenítése."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Elrendezés jobbról balra"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Elrendezés jobbról balra minden nyelvnél"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU-használat mutatása"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Képernyőfedvény a jelenlegi CPU-használattal"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU-megjelenítés"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"GPU használatának kényszerítése 2D rajzhoz"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA kényszerítése"</string>
diff --git a/packages/SettingsLib/res/values-hy-rAM/strings.xml b/packages/SettingsLib/res/values-hy-rAM/strings.xml
index 32b0dd6..9428e17 100644
--- a/packages/SettingsLib/res/values-hy-rAM/strings.xml
+++ b/packages/SettingsLib/res/values-hy-rAM/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi կապի ձախողում"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Նույնականացման խնդիր"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ընդգրկույթից դուրս է"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Չի միանա ավտոմատ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ինտերնետ կապ չկա"</string>
<string name="saved_network" msgid="4352716707126620811">"Պահել է հետևյալ օգտվողը՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Կապակցված է Wi‑Fi Օգնականի միջոցով"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Կապակցված է %1$s-ի միջոցով"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Ցույց տալ կտրվածքի սահմանները, լուսանցքները և այլն"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Փոխել RTL-ի դասավորության ուղղությունը"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Դարձնել էկրանի դասավորության ուղղությունը դեպի RTL բոլոր լեզուների համար"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Ցույց տալ CPU-ի աշխատանքը"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Էկրանի վերադրումը ցույց է տալիս ընթացիկ CPU օգտագործումը"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Ստիպել GPU-ին մատուցել"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Ստիպողաբար GPU-ի օգտագործում 2-րդ պատկերի համար"</string>
<string name="force_msaa" msgid="7920323238677284387">"Ստիպել 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 33df1fe..a382e37 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Kegagalan Sambungan Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Masalah autentikasi"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Tidak dalam jangkauan"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Tidak akan tersambung otomatis"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Tidak ada akses internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Disimpan oleh <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Terhubung melalui Asisten Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Terhubung melalui %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Tampilkan batas klip, margin, dll."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Paksa arah tata letak RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Paksa arah tata letak layar RTL untuk semua lokal"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Tampilkan penggunaan CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Hamparan layar menampilkan penggunaan CPU saat ini"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Paksa perenderan GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Paksa penggunaan GPU untuk gambar 2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Force 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-is-rIS/strings.xml b/packages/SettingsLib/res/values-is-rIS/strings.xml
index 8cc408f..44517a8 100644
--- a/packages/SettingsLib/res/values-is-rIS/strings.xml
+++ b/packages/SettingsLib/res/values-is-rIS/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi-tengingarvilla"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Vandamál við auðkenningu"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ekkert samband"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Mun ekki tengjast sjálfkrafa"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Enginn netaðgangur"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> vistaði"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Tengt í gegnum Wi-Fi aðstoð"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Tengt í gegnum %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Sýna skurðlínur, spássíur o.s.frv."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Þvinga umbrot frá hægri til vinstri"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Þvinga umbrot skjás frá hægri til vinstri fyrir alla tungumálskóða"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Sýna örgjörvanotkun"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Skjáyfirlögn sem sýnir núverandi örgjörvanotkun"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Þvinga skjákortsteiknun"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Þvinga notkun skjákorts fyrir tvívíða teikningu"</string>
<string name="force_msaa" msgid="7920323238677284387">"Þvinga 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 0bb26e1..7a32827 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Errore connessione Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema di autenticazione"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fuori portata"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Non verrà eseguita la connessione automatica"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nessun accesso a Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Salvata da <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Connesso tramite assistente Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Collegato tramite %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostra limiti, margini dei clip e così via"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forza direzione layout RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Direzione layout schermo RTL per tutte le lingue"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostra utilizzo CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Overlay schermo che mostra l\'uso corrente della CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forza rendering GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forza l\'uso della GPU per i disegni 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forza MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 7510911..2e85770 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"כשל בחיבור Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"בעיית אימות"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"מחוץ לטווח"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"לא יתבצע חיבור באופן אוטומטי"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"אין גישה לאינטרנט"</string>
<string name="saved_network" msgid="4352716707126620811">"נשמר על ידי <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"מחובר באמצעות אסיסטנט ה-Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"מחובר דרך %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"הצג גבולות קליפ, שוליים וכו\'"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"אלץ כיוון פריסה מימין לשמאל"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"אלץ כיוון פריסת מסך מימין לשמאל עבור כל השפות בכל המקומות"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"הצג את השימוש ב-CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"שכבת-על של מסך שמציגה את השימוש הנוכחי ב-CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"אלץ עיבוד ב-GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"אכוף שימוש ב-GPU לשרטוט דו-מימדי"</string>
<string name="force_msaa" msgid="7920323238677284387">"אלץ הפעלת 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 812f53d..00e323c 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi接続エラー"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"認証に問題"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"圏外"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"自動的に接続されません"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"インターネットに接続していません"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g>で保存"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fiアシスタント経由で接続"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s経由で接続"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"クリップの境界線、マージンなどを表示"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTLレイアウト方向を使用"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"すべての言語/地域で画面レイアウト方向をRTLに設定"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU使用状況を表示"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"現在のCPU使用状況をオーバーレイ表示する"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPUレンダリングを使用"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2D描画にGPUを常に使用する"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAAを適用"</string>
diff --git a/packages/SettingsLib/res/values-ka-rGE/strings.xml b/packages/SettingsLib/res/values-ka-rGE/strings.xml
index f349c4f..2b9e8cb 100644
--- a/packages/SettingsLib/res/values-ka-rGE/strings.xml
+++ b/packages/SettingsLib/res/values-ka-rGE/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi კავშირის შეფერხება"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ავთენტიკაციის პრობლემა"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"არ არის დიაპაზონში"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"ავტომატურად დაკავშირება ვერ მოხერხდება"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ინტერნეტთან კავშირი არ არის"</string>
<string name="saved_network" msgid="4352716707126620811">"შენახული <xliff:g id="NAME">%1$s</xliff:g>-ის მიერ"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"დაკავშირებულია Wi-Fi თანაშემწით"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s-ით დაკავშირებული"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"კლიპის საზღვრების, მინდვრების ჩვენება და ა.შ."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"მარჯვნიდან მარცხნივ განლაგების მიმართულების იძულება"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"ეკრანის RTL მიმართულებაზე იძულება ყველა ლოკალისათვის"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"ცენტრალური პროცესორის ჩატვირთვის ჩვენება"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"ეკრანის გადაფარვა აჩვენებს CPU ამჟამინდელ გამოყენებას"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU-აჩქარება"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"GPU-ის ძალით გამოყენება 2d drawing-თვის"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA-ს ჩართვა"</string>
diff --git a/packages/SettingsLib/res/values-kk-rKZ/strings.xml b/packages/SettingsLib/res/values-kk-rKZ/strings.xml
index 1141fca..0037c11 100644
--- a/packages/SettingsLib/res/values-kk-rKZ/strings.xml
+++ b/packages/SettingsLib/res/values-kk-rKZ/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi байланысының қатесі"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Растау мәселесі"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Аумақта жоқ"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Автоматты қосылмайды"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Интернетпен байланыс жоқ"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> сақтаған"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi көмекшісі арқылы қосылу орындалды"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s арқылы қосылған"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Қию шектерін, жиектерін, т.б көрсету."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Оңнан солға орналасу бағытына реттеу"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Экранның орналасу бағытын барлық тілдер үшін оңнан солға қарату"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU (орталық өңдеу бірлігі) қолданысы"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Экран бетіне ағымдағы CPU қолданысы көрсетіледі"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU рендерингін жылдамдату"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Графикалық процессорды 2d сызбаларына қолдану"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA қолдану"</string>
diff --git a/packages/SettingsLib/res/values-km-rKH/strings.xml b/packages/SettingsLib/res/values-km-rKH/strings.xml
index 7d1c797..235ea6a 100644
--- a/packages/SettingsLib/res/values-km-rKH/strings.xml
+++ b/packages/SettingsLib/res/values-km-rKH/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"ការភ្ជាប់ WiFi បរាជ័យ"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"បញ្ហាក្នុងការផ្ទៀងផ្ទាត់"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"នៅក្រៅតំបន់"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"នឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិទេ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"មិនមានអ៊ីនធឺណិតទេ"</string>
<string name="saved_network" msgid="4352716707126620811">"បានរក្សាទុកដោយ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"បានភ្ជាប់តាមរយៈជំនួយការ Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"បានភ្ជាប់តាមរយៈ %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"បង្ហាញការភ្ជាប់អត្ថបទសម្រង់ រឹម ។ល។"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"បង្ខំទិសប្លង់ RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"បង្ខំទិសប្លង់អេក្រង់ទៅកាន់ RTL សម្រាប់មូលដ្ឋានទាំងអស់"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"បង្ហាញការប្រើ CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"អេក្រង់ត្រួតគ្នាបង្ហាញការប្រើ CPU បច្ចុប្បន្ន"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"បង្ខំឲ្យបង្ហាញ GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"បង្ខំប្រើ GPU សម្រាប់ការគូរលើកទីពីរ"</string>
<string name="force_msaa" msgid="7920323238677284387">"បង្ខំ 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-kn-rIN/strings.xml b/packages/SettingsLib/res/values-kn-rIN/strings.xml
index 9039e25..d7c8d03 100644
--- a/packages/SettingsLib/res/values-kn-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-kn-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi ಸಂಪರ್ಕ ವಿಫಲತೆ"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ಪ್ರಮಾಣೀಕರಣ ಸಮಸ್ಯೆ"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"ವ್ಯಾಪ್ತಿಯಲ್ಲಿಲ್ಲ"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವಿಲ್ಲ"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> ರಿಂದ ಉಳಿಸಲಾಗಿದೆ"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi ಸಹಾಯಕದ ಮೂಲಕ ಸಂಪರ್ಕಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s ಮೂಲಕ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -74,7 +72,7 @@
<string name="bluetooth_hid_profile_summary_use_for" msgid="232727040453645139">"ಇನ್ಪುಟ್ಗಾಗಿ ಬಳಸು"</string>
<string name="bluetooth_pairing_accept" msgid="6163520056536604875">"ಜೋಡಿ"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"ಜೋಡಿ ಮಾಡು"</string>
- <string name="bluetooth_pairing_decline" msgid="4185420413578948140">"ರದ್ದುಮಾಡು"</string>
+ <string name="bluetooth_pairing_decline" msgid="4185420413578948140">"ರದ್ದುಮಾಡಿ"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"ಸಂಪರ್ಕಪಡಿಸಿದಾಗ, ಜೋಡಿಸುವಿಕೆಯು ನಿಮ್ಮ ಸಂಪರ್ಕಗಳು ಮತ್ತು ಕರೆ ಇತಿಹಾಸಕ್ಕೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಜೊತೆಗೆ ಜೋಡಣೆ ಮಾಡಲಾಗಲಿಲ್ಲ."</string>
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"ತಪ್ಪಾಗಿರುವ ಪಿನ್ ಅಥವಾ ಪಾಸ್ಕೀ ಕಾರಣದಿಂದಾಗಿ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಜೊತೆಗೆ ಜೋಡಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ."</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ಕ್ಲಿಪ್ನ ಗಡಿಗಳು, ಅಂಚುಗಳು, ಇತ್ಯಾದಿ ತೋರಿಸು."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL ಲೇಔಟ್ ಪರಿಮಿತಿ ಬಲಗೊಳಿಸಿ"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"ಎಲ್ಲ ಸ್ಥಳಗಳಿಗಾಗಿ RTL ಗೆ ಸ್ಕ್ರೀನ್ ಲೇಔಟ್ ದಿಕ್ಕನ್ನು ಪ್ರಬಲಗೊಳಿಸಿ"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU ಬಳಕೆಯನ್ನು ತೋರಿಸು"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"ಪ್ರಸ್ತುತ CPU ಬಳಕೆಯನ್ನು ತೋರಿಸುತ್ತಿರುವ ಪರದೆಯ ಓವರ್ಲೇ"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU ನೀಡುವಿಕೆ ಬಲಗೊಳಿಸು"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d ಚಿತ್ರಕಲೆಗಾಗಿ GPU ಬಳಕೆ ಬಲಗೊಳಿಸಿ"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA ಪ್ರಬಲಗೊಳಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index e33b989..21bbc3f 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi 연결 실패"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"인증 문제"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"범위 내에 없음"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"자동으로 연결되지 않습니다."</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"인터넷에 연결되어 있지 않습니다."</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g>(으)로 저장됨"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi 도우미를 통해 연결됨"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s을(를) 통해 연결됨"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"클립 경계, 여백 등을 표시"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL 레이아웃 방향 강제 적용"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"모든 언어에 대해 화면 레이아웃 방향을 RTL로 강제 적용"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU 사용량 표시"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"현재 CPU 사용량 오버레이 표시"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU 렌더링 강제 설정"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2D 드로잉용으로 GPU 강제 사용"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA 강제 사용"</string>
diff --git a/packages/SettingsLib/res/values-ky-rKG/strings.xml b/packages/SettingsLib/res/values-ky-rKG/strings.xml
index 22622dd..ca716cae 100644
--- a/packages/SettingsLib/res/values-ky-rKG/strings.xml
+++ b/packages/SettingsLib/res/values-ky-rKG/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi туташуусу бузулду"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Аутентификация маселеси бар"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Тейлөө аймагында эмес"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Автоматтык түрдө туташпайт"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Интернетке туташпай турат"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> тарабынан сакталды"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi жардамчысы аркылуу туташып турат"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s аркылуу жеткиликтүү"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Клиптин чектерин, талааларын ж.б. көргөзүү"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Солдон оңго багытына мажбурлоо"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Экрандын жайгашуу багытын бардык тилдер үчүн Оңдон-солго кылуу"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU колдонулушун көрсөтүү"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Учурдагы CPU колдонулушун көрсөтүүчү экран катмары"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU иштетүүсүн мажбурлоо"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d тартуу үчүн GPU\'ну колдонууга мажбурлоо"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA мажбурлоо"</string>
diff --git a/packages/SettingsLib/res/values-lo-rLA/strings.xml b/packages/SettingsLib/res/values-lo-rLA/strings.xml
index 716ee6f..0108c2c 100644
--- a/packages/SettingsLib/res/values-lo-rLA/strings.xml
+++ b/packages/SettingsLib/res/values-lo-rLA/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"ການເຊື່ອມຕໍ່ WiFi ລົ້ມເຫຼວ"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ບັນຫາການພິສູດຢືນຢັນ"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"ບໍ່ຢູ່ໃນໄລຍະທີ່ເຊື່ອມຕໍ່ໄດ້"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"ຈະບໍ່ເຊື່ອມຕໍ່ອັດຕະໂນມັດ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
<string name="saved_network" msgid="4352716707126620811">"ບັນທຶກໂດຍ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"ເຊື່ອມຕໍ່ຜ່ານ Wi‑Fi ຕົວຊ່ວຍແລ້ວ"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"ເຊື່ອມຕໍ່ຜ່ານ %1$s ແລ້ວ"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ສະແດງໜ້າປົກຄລິບ, ຂອບ ແລະອື່ນໆ."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"ບັງຄັບໃຫ້ຮູບຮ່າງຂຽນຈາກຂວາຫາຊ້າຍ"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"ບັງຄັບໃຫ້ຮູບຮ່າງໜ້າຈໍ ຂຽນຈາກຂວາໄປຊ້າຍ ສຳລັບທຸກພາສາ"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"ສະແດງການນຳໃຊ້ CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"ການວາງຊ້ອນໜ້າຈໍທີ່ສະແດງການນຳໃຊ້ CPU ໃນປັດຈຸບັນ"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"ບັງຄັບໃຊ້ GPU ປະມວນພາບ"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"ບັງຄັບໃຊ້ GPU ເພື່ອການແຕ້ມພາບ 2 ມິຕິ"</string>
<string name="force_msaa" msgid="7920323238677284387">"ບັງຄັບໃຊ້ 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 167e756..154864f 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"„Wi-Fi“ ryšio triktis"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentifikavimo problema"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ne diapazone"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Nebus automatiškai prisijungiama"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nėra interneto ryšio"</string>
<string name="saved_network" msgid="4352716707126620811">"Išsaugojo <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Prisijungta naudojant „Wi‑Fi“ pagelbiklį"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Prisijungta naudojant „%1$s“"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Rodyti iškarpų ribas, kraštines ir t. t."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Išdėst. iš dešin. į kairę"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Nust. visų lokalių ekran. išdėst. iš deš. į kairę"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Rodyti centr. proc. naud."</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Ekrano perdanga rodo dabartinį centr. proc. naud."</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Priverst. GPU atvaizd."</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Priverstinai naudoti GPU atvaizduojant 2D formatą"</string>
<string name="force_msaa" msgid="7920323238677284387">"Priverst. vykdyti 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 7f5e111..df91cb1 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi savienojuma kļūme"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentificēšanas problēma"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nav diapazona ietvaros"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Savienojums netiks izveidots automātiski"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nav piekļuves internetam"</string>
<string name="saved_network" msgid="4352716707126620811">"Saglabāja: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Izveidots savienojums ar Wi‑Fi palīgu"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Savienots, izmantojot %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Rādīt klipu robežas, malas utt."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Virziens no labās uz kreiso (Obligāts) WL: 295"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Obl. izkārt. virz. no labās uz kr. pusi visām lok."</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Rādīt CPU lietojumu"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Ekrāna pārklājums ar aktuālo CPU lietojumu"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Piespiedu GPU render."</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Izmantot GPU atveidi divdimensiju zīmējumiem"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA piespiedu palaiš."</string>
diff --git a/packages/SettingsLib/res/values-mk-rMK/strings.xml b/packages/SettingsLib/res/values-mk-rMK/strings.xml
index 2a454d2..42bc33f 100644
--- a/packages/SettingsLib/res/values-mk-rMK/strings.xml
+++ b/packages/SettingsLib/res/values-mk-rMK/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Поврзувањето преку Wi-Fi не успеа"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Проблем со автентикација"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Надвор од опсег"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Не може да се поврзе автоматски"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Нема пристап до Интернет"</string>
<string name="saved_network" msgid="4352716707126620811">"Зачувано од <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Поврзано преку помошник за Wi-Fismile"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Поврзано преку %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Прикажи граници на клип, маргини, итн."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Сила на RTL за насока на слој"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Присилно постави насока на распоред на екран во РТЛ за сите локални стандарди"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Прикажи употреба на ЦПУ"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Прекривка на екран прикаж. употреба на тековен ЦПУ"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Присили рендерирање на GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Присилно користење на GPU за цртеж 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Сила 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ml-rIN/strings.xml b/packages/SettingsLib/res/values-ml-rIN/strings.xml
index 56d1477..a6017d5 100644
--- a/packages/SettingsLib/res/values-ml-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-ml-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi കണക്ഷൻ പരാജയം"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ആധികാരികമാക്കുന്നതിലെ പ്രശ്നം"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"പരിധിയിലില്ല"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"സ്വയമേവ കണക്റ്റുചെയ്യില്ല"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ഇന്റർനെറ്റ് ആക്സസ്സ് ഇല്ല"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> സംരക്ഷിച്ചത്"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"വൈഫൈ അസിസ്റ്റന്റ് മുഖേന കണക്റ്റുചെയ്തു"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s വഴി ബന്ധിപ്പിച്ചു"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ക്ലിപ്പ് ബൗണ്ടുകൾ, മാർജിനുകൾ തുടങ്ങിയവ ദൃശ്യമാക്കുക"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL ലേഔട്ട് ഡയറക്ഷൻ നിർബന്ധമാക്കുക"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"എല്ലാ ഭാഷകൾക്കുമായി സ്ക്രീൻ ലേഔട്ട് ഡയറക്ഷൻ RTL-ലേക്ക് നിർബന്ധമാക്കുക"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU ഉപയോഗം ദൃശ്യമാക്കുക"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"സ്ക്രീൻ ഓവർലേ നിലവിലെ CPU ഉപയോഗം ദൃശ്യമാക്കുന്നു"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU റെൻഡറിംഗ് ഫോഴ്സ്ചെയ്യുക"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d ഡ്രോയിംഗിനായുള്ള നിരബന്ധിത GPU ഉപയോഗം"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA നിർബന്ധമാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn-rMN/strings.xml b/packages/SettingsLib/res/values-mn-rMN/strings.xml
index 9848cae..cb6327a 100644
--- a/packages/SettingsLib/res/values-mn-rMN/strings.xml
+++ b/packages/SettingsLib/res/values-mn-rMN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi холболт амжилтгүй"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Гэрчлэлийн асуудал"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Хүрээнд байхгүй"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Автоматаар холбогдохгүй"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Интернэт холболт алга"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> хадгалсан"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi-Fi туслагчаар дамжуулан холбогдлоо"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s-р холбогдсон"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Клипийн зах, хязгаар зэргийг харуулах"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL байрлалын чиглэлийг хүчээр тогтоох"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Бүх локалын хувьд дэлгэцийн байрлалын чиглэлийг хүчээр RTL болгох"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU ашиглалтыг харуулах"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Дэлгэцийн давхцалаар одоогийн CPU ашиглалтыг харуулж байна"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Хүчээр GPU ашиглах"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"GPU-г 2d зурагт хүчээр ашиглах"</string>
<string name="force_msaa" msgid="7920323238677284387">"Хүчээр 4x MSAA ашиглах"</string>
diff --git a/packages/SettingsLib/res/values-mr-rIN/strings.xml b/packages/SettingsLib/res/values-mr-rIN/strings.xml
index 83ae663..b6adb4f 100644
--- a/packages/SettingsLib/res/values-mr-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-mr-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi कनेक्शन अयशस्वी"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"प्रमाणीकरण समस्या"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"परिक्षेत्रामध्ये नाही"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"स्वयंचलितपणे कनेक्ट करणार नाही"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"इंटरनेट प्रवेश नाही"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे जतन केले"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi सहाय्यक द्वारे कनेक्ट केले"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s द्वारे कनेक्ट केले"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"क्लिप सीमा, समास इत्यादी दर्शवा."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL लेआउट दिशानिर्देशाची सक्ती करा"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"सर्व लोकॅलसाठी RTL स्क्रीन लेआउट दिशानिर्देशाची सक्ती करा"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU वापर दर्शवा"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"वर्तमान CPU वापर दर्शविणारे स्क्रीन आच्छादन"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU प्रस्तुतीस सक्ती करा"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d रेखांकनासाठी GPU च्या वापराची सक्ती करा"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA ची सक्ती करा"</string>
diff --git a/packages/SettingsLib/res/values-ms-rMY/strings.xml b/packages/SettingsLib/res/values-ms-rMY/strings.xml
index 217d82a..9886d3e 100644
--- a/packages/SettingsLib/res/values-ms-rMY/strings.xml
+++ b/packages/SettingsLib/res/values-ms-rMY/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Kegagalan Sambungan WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Masalah pengesahan"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Tidak dalam liputan"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Tidak akan menyambung secara automatik"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Tiada akses Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Diselamatkan oleh <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Disambungkan melalui Pembantu Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Disambungkan melalui %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Tunjukkan batas klip, margin dll."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Paksa arah reka letak RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Paksa arah reka letak skrin RTL bagi semua tempat peristiwa"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Tunjukkan penggunaan CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Tindihan skrin menunjukkan penggunaan semasa CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Paksa pemaparan GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Paksa penggunaan GPU untuk lukisan 2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Paksa 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-my-rMM/strings.xml b/packages/SettingsLib/res/values-my-rMM/strings.xml
index fc977a9..935ad40 100644
--- a/packages/SettingsLib/res/values-my-rMM/strings.xml
+++ b/packages/SettingsLib/res/values-my-rMM/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi ချိတ်ဆက်မှု မအောင်မြင်ပါ"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"စစ်မှန်ကြောင်းအတည်ပြုရန်၌ ပြသနာရှိခြင်း"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"စက်ကွင်းထဲတွင် မဟုတ်ပါ"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"အလိုအလျောက်ချိတ်ဆက်မည်မဟုတ်ပါ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"အင်တာနက် ချိတ်ဆက်မှု မရှိပါ"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> မှသိမ်းဆည်းခဲ့သည်"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"ကြိုးမဲ့ကူညီသူမှတဆင့် ချိတ်ဆက်၏"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ဖြတ်ပိုင်းအနားသတ်များ၊ အနားများ စသဖြင့် ပြပါ။"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL ဖွဲ့စည်းပုံအညွှန်း မဖြစ်မနေလုပ်ပါ"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"လိုကယ်လ်အားလုံးအတွက် မျက်နှာပြင် ဖွဲ့စည်းပုံအညွှန်း မဖြစ်မနေလုပ်ရန်"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPUအသုံးပြုမှုအား ပြသရန်"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"လက်ရှိCPUအသုံးပြုမှုအားလုံး မျက်နှာပြင်တွင်ပြသမှု"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPUအား အတင်းအကျပ်ဖြစ်စေမည်"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"GPUကို ၂ဖက်မြင်ပုံဆွဲခြင်းအတွက် မဖြစ်မနေအသုံးပြုစေရန်"</string>
<string name="force_msaa" msgid="7920323238677284387">"တွန်းအား ၄× MSAA"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index bd6ae05..18b6f60 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi-tilkoblingsfeil"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentiseringsproblem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Utenfor område"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Kobler ikke til automatisk"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ingen Internett-tilgang"</string>
<string name="saved_network" msgid="4352716707126620811">"Lagret av <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Koblet til via en Wi-Fi-assistent"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Tilkoblet via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Vis kanter, marger osv."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Tving layoutretning for RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Tving RTL-retning på skjermen for alle språk"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Vis CPU-bruk"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Skjermoverlegg viser gjeldende CPU-bruk"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Tving GPU-gjengivelse"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Tving bruk av GPU for 2D-tegning"</string>
<string name="force_msaa" msgid="7920323238677284387">"Tving 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ne-rNP/strings.xml b/packages/SettingsLib/res/values-ne-rNP/strings.xml
index c671f90..478e19d 100644
--- a/packages/SettingsLib/res/values-ne-rNP/strings.xml
+++ b/packages/SettingsLib/res/values-ne-rNP/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"वाईफाई जडान असफल"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"प्रमाणीकरण समस्या"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"दायराभित्र छैन"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"स्वतः जडान हुने छैन"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"इन्टरनेट माथिको पहुँच छैन"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारा सुरक्षित गरियो"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi-Fi सहायक द्वारा जोडिएको"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s मार्फत जडित"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"क्लिप सीमा, मार्जिन, इत्यादि देखाउनुहोस्।"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL लेआउट दिशामा जबर्जस्ती गर्नुहोस्"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"सबै लोकेलहरूको लागि RTLमा स्क्रिन लेआउट दिशामा जबर्जस्ती गर्नुहोस्"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU उपयोग देखाउनुहोस्"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"स्क्रिन ओभरले वर्तमान CPU प्रयोग देखाउँदै"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU रेन्डर गर्न जोड गर्नुहोस्"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d चित्र कोर्नका लागि GPU को प्रयोगलाई जोड दिनुहोस्"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA जोड गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index e1bc58d..8f73bf9 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -50,12 +50,12 @@
</string-array>
<string-array name="hdcp_checking_titles">
<item msgid="441827799230089869">"Nooit controleren"</item>
- <item msgid="6042769699089883931">"Alleen controleren op DRM-inhoud"</item>
+ <item msgid="6042769699089883931">"Alleen controleren op DRM-content"</item>
<item msgid="9174900380056846820">"Altijd controleren"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="505558545611516707">"HDCP-controle nooit gebruiken"</item>
- <item msgid="3878793616631049349">"HDCP-controle alleen voor DRM-inhoud gebruiken"</item>
+ <item msgid="3878793616631049349">"HDCP-controle alleen voor DRM-content gebruiken"</item>
<item msgid="45075631231212732">"HDCP-controle altijd gebruiken"</item>
</string-array>
<string-array name="select_logd_size_titles">
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 330eb30..78e1ee1 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wifi-verbinding mislukt"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Authenticatieprobleem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Niet binnen bereik"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Er wordt niet automatisch verbinding gemaakt"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Geen internettoegang"</string>
<string name="saved_network" msgid="4352716707126620811">"Opgeslagen door <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Verbonden via wifi-assistent"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Verbonden via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Clipgrenzen, marges en meer weergeven"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"V.r.n.l.-indelingsrichting afdwingen"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Schermindelingsrichting geforceerd instellen op v.r.n.l. voor alle talen"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU-gebruik weergeven"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Schermoverlay met huidig CPU-gebruik"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU-rendering afdwingen"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Gebruik van GPU voor 2D-tekening forceren"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA forceren"</string>
diff --git a/packages/SettingsLib/res/values-pa-rIN/strings.xml b/packages/SettingsLib/res/values-pa-rIN/strings.xml
index 9826023..b678aac 100644
--- a/packages/SettingsLib/res/values-pa-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-pa-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi ਕਨੈਕਸ਼ਨ ਅਸਫਲਤਾ"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ਪ੍ਰਮਾਣੀਕਰਨ ਸਮੱਸਿਆ"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"ਰੇਂਜ ਵਿੱਚ ਨਹੀਂ ਹੈ"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ਕੋਈ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> ਵੱਲੋਂ ਸੁਰੱਖਿਅਤ ਕੀਤਾ"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi ਸਹਾਇਕ ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ਕਲਿਪ ਬਾਊਂਡਸ, ਮਾਰਜਿਨ ਆਦਿ ਦਿਖਾਓ"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL ਲੇਆਉਟ ਦਿਸ਼ਾ ਤੇ ਜ਼ੋਰ ਪਾਓ"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"ਸਾਰੇ ਸਥਾਨਾਂ ਲਈ RTL ਵੱਲ ਸਕ੍ਰੀਨ ਲੇਆਉਟ ਦਿਸ਼ਾ ਤੇ ਜ਼ੋਰ ਪਾਓ"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU ਵਰਤੋਂ ਦਿਖਾਓ"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"ਸਕ੍ਰੀਨ ਓਵਰਲੇ ਵਰਤਮਾਨ CPU ਵਰਤੋਂ ਦਿਖਾ ਰਿਹਾ ਹੈ"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU ਪ੍ਰਗਟਾਅ ਤੇ ਜ਼ੋਰ ਪਾਓ"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d ਡ੍ਰਾਇੰਗ ਲਈ GPU ਦੀ ਵਰਤੋਂ ਤੇ ਜ਼ੋਰ ਪਾਓ"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA ਤੇ ਜ਼ੋਰ ਪਾਓ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 4dce1a4..77a23f2 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Błąd połączenia Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problem z uwierzytelnianiem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Poza zasięgiem"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Nie można połączyć automatycznie"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Brak dostępu do internetu"</string>
<string name="saved_network" msgid="4352716707126620811">"Zapisane przez: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Połączono przez Asystenta Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Połączono przez %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Pokaż granice przycięcia, marginesy itd."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Układ od prawej do lewej"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Wymuś wszędzie układ ekranu od prawej do lewej"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Pokaż użycie procesora"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Nakładka na ekranie pokazująca użycie procesora"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Renderowanie na GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Wymuszaj użycie GPU do rysowania 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Wymuś 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 27332f9..588ca67 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Falha de conexão Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticação"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fora do alcance"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Não se conectará automaticamente"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Sem acesso à Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Salvas por <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Conectado via assistente de Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostrar limites de corte, margens, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forçar dir. layout (RTL)"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forçar direção do layout (RTL) p/ todas as local."</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostrar o uso da CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Sobreposição de tela que mostra o uso da CPU atual"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forçar renderização GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forçar uso da GPU para desenho em 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forçar 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index c48ccba..763cb70 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Falha de ligação Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticação"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fora do alcance"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Não é efetuada uma ligação automaticamente"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Sem acesso à Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Guardada por <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Ligado através do Assistente de Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Ligado através de %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Apresentar limites de clipes, margens, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forçar dir. do esq. RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forçar dir. do esq. do ecrã p. RTL tds os locais"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostrar utilização da CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Sobrep. de ecrã que mostra a utiliz. atual da CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forçar composição GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forçar a utilização de GPU para desenho 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forçar 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 27332f9..588ca67 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Falha de conexão Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticação"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Fora do alcance"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Não se conectará automaticamente"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Sem acesso à Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Salvas por <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Conectado via assistente de Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Mostrar limites de corte, margens, etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Forçar dir. layout (RTL)"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Forçar direção do layout (RTL) p/ todas as local."</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Mostrar o uso da CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Sobreposição de tela que mostra o uso da CPU atual"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forçar renderização GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forçar uso da GPU para desenho em 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forçar 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 8458557..6a2122a 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Eroare de conexiune Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problemă la autentificare"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"În afara ariei de acoperire"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Nu se va conecta automat"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nu există acces la internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Salvată de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Conexiune realizată printr-un asistent Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectată prin %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Afișați limitele clipului, marginile etc."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Direcție aspect dreapta - stânga"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Direcție obligatorie aspect ecran dreapta - stânga"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Afișați utiliz. procesor"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Suprapunere care indică utilizare curentă procesor"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Forțați redarea cu GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Forțați utilizarea GPU pentru desen în 2D"</string>
<string name="force_msaa" msgid="7920323238677284387">"Forțați MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index a89c553..1f192c1 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Ошибка подключения Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Ошибка аутентификации"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Недоступна"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Подключение не будет выполняться автоматически"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Отсутствует подключение к Интернету"</string>
<string name="saved_network" msgid="4352716707126620811">"Кто сохранил: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Установлено подключение через Ассистента Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Подключено к %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Показывать границы клипа, поля и т. д."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Написание справа налево"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Включить написание справа налево для всех языков"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Показывать загрузку ЦП"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Экран, показывающий текущую загрузку ЦП"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU-ускорение"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Всегда использовать GPU для двухмерного рисования"</string>
<string name="force_msaa" msgid="7920323238677284387">"Включить 4x MSAA"</string>
@@ -249,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-si-rLK/strings.xml b/packages/SettingsLib/res/values-si-rLK/strings.xml
index c8a0bad..e415544 100644
--- a/packages/SettingsLib/res/values-si-rLK/strings.xml
+++ b/packages/SettingsLib/res/values-si-rLK/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi සම්බන්ධතාව අසාර්ථකයි"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"සත්යාපනයේ ගැටලුවකි"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"පරාසයේ නැත"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"ස්වයංක්රිය නැවත සම්බන්ධ නොවනු ඇත"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"අන්තර්ජාල ප්රවේශය නැත"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> විසින් සුරකින ලදී"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi සහායක හරහා සම්බන්ධ කරන ලදි"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s හරහා සම්බන්ධ විය"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"ක්ලිප් සීමා, මායිම්, ආදිය පෙන්වන්න."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"බල RTL පිරිසැලසුම් දිශාව"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"සියලු පෙදෙසි සඳහා RTL වෙත බල තිර පිරිසැලසුම"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU භාවිතය පෙන්වන්න"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"තීර උඩැතිරිය වත්මන් CPU භාවිතය පෙන්නුම් කරයි"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU විදහාපෑම බලකරන්න"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d ඇඳීම් සඳහා GPU බලයෙන් භාවිතා කරන්න"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA බල කරන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 958bae6..7e636fe7 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Zlyhanie pripojenia Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problém s overením totožnosti"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Mimo dosah"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Nedôjde k automatickému pripojeniu"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Žiadny prístup k internetu"</string>
<string name="saved_network" msgid="4352716707126620811">"Uložil(a) <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Pripojené pomocou Asistenta Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Pripojené prostredníctvom %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Zobraziť vo výstrižku ohraničenie, okraje a pod."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Rozloženia sprava doľava"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Vynútiť pre všetky jazyky rozloženie obrazovky sprava doľava"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Zobraziť využitie CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Prekryvná vrstva s aktuálnym využitím procesora"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Vykresľovat pomocou GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Používať GPU na dvojrozmerné vykresľovanie"</string>
<string name="force_msaa" msgid="7920323238677284387">"Vynútiť 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index d4ad802..78fa3fd 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Povezava prek Wi-Fi-ja ni uspela"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Težava s preverjanjem pristnosti"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ni v obsegu"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Samodejna vnovična vzpostavitev povezave se ne bo izvedla"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ni dostopa do interneta"</string>
<string name="saved_network" msgid="4352716707126620811">"Shranil(-a): <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Povezava vzpostavljena prek pomočnika za Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Vzpostavljena povezava prek: %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Pokaži meje obrezovanja, obrobe ipd."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Vsili od desne proti levi"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Vsili smer postavitve na zaslonu od desne proti levi za vse jezike"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Prikaži uporabo CPE-ja"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Prekrivanje zaslona prikazuje tren. uporabo CPE-ja"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Vsili upodabljanje z GPE-jem"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Za risanje 2D vsili uporabo grafičnega procesorja"</string>
<string name="force_msaa" msgid="7920323238677284387">"Vsili 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-sq-rAL/strings.xml b/packages/SettingsLib/res/values-sq-rAL/strings.xml
index ba5d2833..ad4cf61 100644
--- a/packages/SettingsLib/res/values-sq-rAL/strings.xml
+++ b/packages/SettingsLib/res/values-sq-rAL/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Dështim i lidhjes WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problem me vërtetimin"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Nuk është brenda rrezes"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Nuk do të lidhet automatikisht"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Nuk ka qsaje në internet"</string>
<string name="saved_network" msgid="4352716707126620811">"E ruajtur nga <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"I lidhur nëpërmjet ndihmësit të Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"E lidhur përmes %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Shfaq konturet e klipit, hapësirat etj."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Detyro drejtimin e shkrimit nga e djathta në të majtë"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Ndrysho me detyrim drejtimin e planit të ekranit nga e djathta në të majtë për të gjitha vendet"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Shfaq përdorimin e CPU-së"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Mbivendosja e ekranit tregon përdorimin e CPU-së"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Detyro interpretimin e GPU-së"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Detyro përdorimin e GPU-së për vizatimin e dytë"</string>
<string name="force_msaa" msgid="7920323238677284387">"Detyro 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b04a4c2..02eb615 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi веза је отказала"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Проблем са потврдом аутентичности"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Није у опсегу"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Аутоматско повезивање није успело"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Нема приступа интернету"</string>
<string name="saved_network" msgid="4352716707126620811">"Сачувао/ла је <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Повезано преко Wi‑Fi помоћника"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Веза је успостављена преко приступне тачке %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Прикажи границе клипа, маргине итд."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Наметни смер распореда здесна налево"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Наметни смер распореда екрана здесна налево за све локалитете"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Прик. употребу процесора"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Постав. елемент са тренутном употребом процесора"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Принудни приказ пом. GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Принудно користи GPU за 2D цртање"</string>
<string name="force_msaa" msgid="7920323238677284387">"Наметни 4x MSAA"</string>
@@ -285,7 +281,7 @@
<string name="enable_webview_multiprocess_desc" msgid="2485604010404197724">"Покрећите WebView приказиваче засебно"</string>
<string name="select_webview_provider_title" msgid="4628592979751918907">"Примена WebView-а"</string>
<string name="select_webview_provider_dialog_title" msgid="4370551378720004872">"Подесите примену WebView-а"</string>
- <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"Овај избор више није важећи. Покушајте поново."</string>
+ <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"Овај избор више није важећи. Пробајте поново."</string>
<string name="convert_to_file_encryption" msgid="3060156730651061223">"Конвертуј у шифровање датотека"</string>
<string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"Конвертуј..."</string>
<string name="convert_to_file_encryption_done" msgid="7859766358000523953">"Већ се користи шифровање датотека"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 7562ee0..a936c3e 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi-anslutningsfel"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Autentiseringsproblem"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Utom räckhåll"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Det går inte att ansluta automatiskt"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Ingen internetåtkomst"</string>
<string name="saved_network" msgid="4352716707126620811">"Sparades av <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Ansluten via Wi-Fi-assistent"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Anslutet via %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Visa gränser för videoklipp, marginaler m.m."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Tvinga fram RTL-layout"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Tvinga fram RTL-skärmlayout (hö–vä) för alla språk"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Visa CPU-användning"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Överlägg på skärmen med aktuell CPU-användning"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Framtvinga GPU-rendering"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Tvingad användning av GPU för 2D-ritning"</string>
<string name="force_msaa" msgid="7920323238677284387">"Force 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 9e64b91..25f79b6 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Haikuweza Kuunganisha kwenye WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Tatizo la uthibitishaji"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Haiko karibu"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Haiwezi kuunganisha kiotomatiki"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Hakuna muunganisho wa Intaneti"</string>
<string name="saved_network" msgid="4352716707126620811">"Ilihifadhiwa na <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Imeunganishwa kupitia Kisaidizi cha Wi-Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Imeunganishwa kupitia %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Onyesha mipaka ya picha, kingo, nk."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Lazimisha uelekezaji wa muundo wa RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Lazimisha uelekezaji wa muundo wa skrini kwa RTL kwa lugha zote"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Onyesha matumizi ya CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Kuegeshwa kwa skrini ikionyesha matumizi ya sasa ya CPU"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Lazimisha kutungiliza GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Lazimisha matumizi ya GPU kwa uchoraji wa 2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Lazimisha 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ta-rIN/strings.xml b/packages/SettingsLib/res/values-ta-rIN/strings.xml
index b9ff6bc..9e9e92c 100644
--- a/packages/SettingsLib/res/values-ta-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-ta-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"வைஃபை இணைப்பில் தோல்வி"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"அங்கீகரிப்புச் சிக்கல்"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"தொடர்பு எல்லையில் இல்லை"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"தானாக இணைக்கப்படாது"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"இணைய அணுகல் இல்லை"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> சேமித்தது"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"வைஃபை அசிஸ்டண்ட் மூலம் இணைக்கப்பட்டது"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s வழியாக இணைக்கப்பட்டது"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"கிளிப் எல்லைகள், ஓரங்கள், மேலும் பலவற்றைக் காட்டு"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL தளவமைப்பின் திசையை வலியுறுத்து"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"எல்லா மொழிகளுக்கும் திரையின் தளவமைப்பு திசையை RTL க்கு மாற்று"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU பயன்பாட்டைக் காட்டு"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"தற்போதைய CPU பயன்பாட்டைக் காட்டும் திரை மேலடுக்கு"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU காட்சியாக்கத்தை வலியுறுத்து"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d வரைபடத்திற்கு GPU பயன்பாட்டை வலியுறுத்து"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA ஐ வலியுறுத்து"</string>
diff --git a/packages/SettingsLib/res/values-te-rIN/strings.xml b/packages/SettingsLib/res/values-te-rIN/strings.xml
index 08bcbda..27f8e09 100644
--- a/packages/SettingsLib/res/values-te-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-te-rIN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi కనెక్షన్ వైఫల్యం"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ప్రామాణీకరణ సమస్య"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"పరిధిలో లేదు"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"స్వయంచాలకంగా కనెక్ట్ కాదు"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ఇంటర్నెట్ ప్రాప్యత లేదు"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా సేవ్ చేయబడింది"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi సహాయకం ద్వారా కనెక్ట్ చేయబడింది"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s ద్వారా కనెక్ట్ చేయబడింది"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"క్లిప్ సరిహద్దులు, అంచులు మొ. చూపు"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL లేఅవుట్ దిశను నిర్భందం చేయండి"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"అన్ని లొకేల్ల కోసం RTLకి స్క్రీన్ లేఅవుట్ దిశను నిర్భందించు"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU వినియోగాన్ని చూపు"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"ప్రస్తుత CPU వినియోగాన్ని చూపేలా స్క్రీన్ అతివ్యాప్తి చేయబడుతుంది"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"నిర్బంధంగా GPU భాషాంతరీకరణ"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2d డ్రాయింగ్ కోసం GPU నిర్భంద వినియోగం"</string>
<string name="force_msaa" msgid="7920323238677284387">"నిర్భందం 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index bd9d8d5..6005972 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"การเชื่อมต่อ Wi-Fi ล้มเหลว"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"ปัญหาในการตรวจสอบสิทธิ์"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"ไม่อยู่ในพื้นที่ให้บริการ"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"จะไม่เชื่อมต่อโดยอัตโนมัติ"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"ไม่สามารถเข้าถึงอินเทอร์เน็ต"</string>
<string name="saved_network" msgid="4352716707126620811">"บันทึกโดย <xliff:g id="NAME">%1$s</xliff:g> แล้ว"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"เชื่อมต่อผ่านตัวช่วย Wi-Fi อยู่"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"เชื่อมต่อผ่าน %1$s แล้ว"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"แสดงหน้าปกคลิป ขอบ ฯลฯ"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"บังคับทิศทางการจัดวาง RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"บังคับทิศทางการจัดวางหน้าจอเป็น RTL สำหรับทุกภาษา"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"แสดงการใช้ CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"การวางซ้อนหน้าจอที่แสดงการใช้ CPU ในปัจจุบัน"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"เร่งการแสดงผลของ GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"ต้องใช้ GPU สำหรับการวาดภาพ 2 มิติ"</string>
<string name="force_msaa" msgid="7920323238677284387">"บังคับใช้ 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 9c952f5..e9feded 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Pagkabigo ng Koneksyon sa WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema sa pagpapatotoo"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Wala sa sakop"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Hindi awtomatikong kokonekta"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Walang access sa Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Na-save ni <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Nakakonekta sa pamamagitan ng Wi‑Fi assistant"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Nakakonekta sa pamamagitan ng %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Ipakita ang mga hangganan ng clip, margin, atbp."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Force RTL layout dir."</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Force screen layout dir. sa RTL sa lahat ng lokal"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Ipakita paggamit ng CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Ipinapakita ng screen overlay ang paggamit ng CPU ngayon"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Ipilit ang pag-render ng GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Sapilitang paggamit sa GPU para sa 2d na pagguhit"</string>
<string name="force_msaa" msgid="7920323238677284387">"Puwersahin ang 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 889b631..0e73b7b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Kablosuz Bağlantı Hatası"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Kimlik doğrulama sorunu"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Kapsama alanı dışında"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Otomatik olarak bağlanma"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"İnternet erişimi yok"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> tarafından kaydedildi"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Kablosuz bağlantı yardımcısıyla bağlandı"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s üzerinden bağlı"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Klip sınırlarını, kenar boşluklarını vb. göster"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Sağdan sola düzenini zorla"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Tüm yerel ayarlar için sağdan sola ekran düzenini zorlar"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU kullanımını göster"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Mevcut CPU kullanımını gösteren yer paylaşımı"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU oluşturmayı zorla"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2D çizimde GPU kullanımını zorla"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA\'yı zorla"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index cdf725f..1015e1c 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Помилка з’єднання Wi-Fi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Проблема з автентифікацією"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Не в діапазоні"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Не під’єднуватиметься автоматично"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Немає доступу до Інтернету"</string>
<string name="saved_network" msgid="4352716707126620811">"Збережено додатком <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Під’єднано через Диспетчер Wi-Fi-з’єднання"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Під’єднано через %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Показувати межі роликів, поля тощо"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Макет письма справа наліво"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Застосовувати макет письма справа наліво для всіх мов"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Показати використання ЦП"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Показувати на екрані поточне використання ЦП"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Примусова візуалізація GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Примусово використовувати GPU для 2D-малювання"</string>
<string name="force_msaa" msgid="7920323238677284387">"Примус. запустити 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-ur-rPK/strings.xml b/packages/SettingsLib/res/values-ur-rPK/strings.xml
index c5aaaab..61dc5ab 100644
--- a/packages/SettingsLib/res/values-ur-rPK/strings.xml
+++ b/packages/SettingsLib/res/values-ur-rPK/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi کنکشن کی ناکامی"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"توثیق کا مسئلہ"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"رینج میں نہیں ہے"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"خودکار طور پر منسلک نہیں ہو گا"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"انٹرنیٹ تک کوئی رسائی نہیں"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> کی جانب سے محفوظ کردہ"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi اسسٹنٹ کے ذریعے منسلک ہے"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"منسلک بذریعہ %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"کلپ باؤنڈز، حاشیے وغیرہ دکھائیں"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"RTL لے آؤٹ سمت زبردستی نافذ کریں"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"سبھی زبانوں کیلئے اسکرین لے آؤٹ کی سمت کو RTL پر مجبور کریں"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"CPU استعمال دکھائیں"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"موجودہ CPU استعمال دکھانے والا اسکرین اوورلے"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU رینڈرنگ زبردستی نافذ کریں"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"2D ڈرائنگ کیلئے GPU کا استعمال زبردستی نافذ کریں"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAA زبردستی نافذ کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz-rUZ/strings.xml b/packages/SettingsLib/res/values-uz-rUZ/strings.xml
index e2c58a4..a6f9ab8 100644
--- a/packages/SettingsLib/res/values-uz-rUZ/strings.xml
+++ b/packages/SettingsLib/res/values-uz-rUZ/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Wi-Fi ulanishini o‘rnatib bo‘lmadi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Tasdiqdan o‘tishda muammo"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Aloqada emas"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Avtomatik ravishda ulanilmaydi"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Internet aloqasi yo‘q"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> tomonidan saqlangan"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Wi‑Fi yordamchisi orqali ulangan"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s orqali ulangan"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Klip, maydon va h.k. chegaralarini ko‘rsatish"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"O‘ngdan chapga qarab yozish"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Barcha tillarda o‘ngdan chapga qarab yozish"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"MP yuklanishini ko‘rsatish"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Joriy MP yuklanishini ko‘rsatuvchi ekran"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"GPU yordamida tezlatish"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Ikki o‘lchamli chizma uchun doim GPU ishlatilsin"</string>
<string name="force_msaa" msgid="7920323238677284387">"4x MSAAni yoqish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index fa4174b..f09e0e5 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Lỗi kết nối WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Sự cố xác thực"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ngoài vùng phủ sóng"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Sẽ không tự động kết nối"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Không có quyền truy cập Internet"</string>
<string name="saved_network" msgid="4352716707126620811">"Được lưu bởi <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Được kết nối qua trình hỗ trợ Wi‑Fi"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Được kết nối qua %1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Hiển thị viền đoạn video, lề, v.v.."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Buộc hướng bố cục RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Buộc hướng bố cục màn hình RTL cho tất cả ngôn ngữ"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Hiển thị mức sử dụng CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Lớp phủ màn hình hiển thị mức sử dụng CPU hiện tại"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Bắt buộc kết xuất GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Bắt buộc sử dụng GPU cho bản vẽ 2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Bắt buộc 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 63199e8..7268dcd 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WLAN 连接失败"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"身份验证出现问题"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"不在范围内"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"无法自动连接"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"无法连接到互联网"</string>
<string name="saved_network" msgid="4352716707126620811">"已通过<xliff:g id="NAME">%1$s</xliff:g>保存"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"已连接(通过 WLAN 助手)"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"已通过%1$s连接"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"显示剪辑边界、边距等。"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"强制使用从右到左的布局方向"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"强制将所有语言区域的屏幕布局方向改为从右到左"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"显示 CPU 使用情况"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"屏幕叠加层显示当前 CPU 使用情况"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"强制进行 GPU 渲染"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"强制使用 GPU 进行 2D 绘图"</string>
<string name="force_msaa" msgid="7920323238677284387">"强制启用 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index edbffc1..ac9de74 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi 連線失敗"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"驗證問題"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"超出可用範圍"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"不會自動連線"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"無法偵測互聯網連線"</string>
<string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> 的儲存"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"已透過 Wi-Fi 小幫手連線"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"已透過 %1$s 連線"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"顯示剪輯範圍、邊界等"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"強制使用從右至左的版面配置方向"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"強制將所有語言代碼的畫面配置方向改為從右至左"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"顯示 CPU 使用量"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"在螢幕上重疊顯示目前的 CPU 使用量"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"強制使用 GPU 轉譯"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"強制使用 GPU 進行 2D 繪圖"</string>
<string name="force_msaa" msgid="7920323238677284387">"強制 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index b1c5f06..f17b523 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"WiFi 連線失敗"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"驗證問題"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"不在有效範圍內"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"無法自動連線"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"沒有可用的網際網路連線"</string>
<string name="saved_network" msgid="4352716707126620811">"由<xliff:g id="NAME">%1$s</xliff:g>儲存"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"已透過 Wi‑Fi 小幫手連線"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"已透過 %1$s 連線"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"顯示剪輯範圍、邊界等。"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"強制使用從右至左版面配置方向"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"強制將所有語言代碼的畫面配置方向改為從右至左"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"顯示 CPU 使用量"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"在螢幕上方顯示目前的 CPU 使用量"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"強制使用 GPU 轉譯"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"強制使用 GPU 進行 2D 繪圖"</string>
<string name="force_msaa" msgid="7920323238677284387">"強制 4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e4f0d59..1fc551f 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -28,10 +28,8 @@
<string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Ukwehlulekla koxhumo le-WiFi"</string>
<string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Inkinga yokufakazela ubuqiniso"</string>
<string name="wifi_not_in_range" msgid="1136191511238508967">"Ayikho ebubanzini"</string>
- <!-- no translation found for wifi_no_internet_no_reconnect (2211781637653149657) -->
- <skip />
- <!-- no translation found for wifi_no_internet (5011955173375805204) -->
- <skip />
+ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"Ngeke ize ixhumeke ngokuzenzakalela"</string>
+ <string name="wifi_no_internet" msgid="3880396223819116454">"Akukho ukufinyelela ku-inthanethi"</string>
<string name="saved_network" msgid="4352716707126620811">"Kulondolozwe ngu-<xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_wfa" msgid="3805736726317410714">"Ixhunywe ngomsizi we-Wi-FI"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"Kuxhumeke nge-%1$s"</string>
@@ -236,8 +234,6 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Bonisa imikhawulo, imiphetho, njll, yesiqeshana."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Phoqelela isikhombisi-ndlela sesakhiwo se-RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Phoqelela isikhombisi-ndlela sesikrini ku-RTL kuzo zonke izifunda"</string>
- <string name="show_cpu_usage" msgid="2389212910758076024">"Bonisa ukusebenzisa i-CPU"</string>
- <string name="show_cpu_usage_summary" msgid="2113341923988958266">"Imbondela yesikrini ibonisa ukusetshenziswa kwe-CPU okwamanje"</string>
<string name="force_hw_ui" msgid="6426383462520888732">"Phoqa ukunikeza i-GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Phoqelela ukusetshenziswa kwe-GPU ngomdwebo we-2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Phoqelela i-4x MSAA"</string>
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 0cf4a41..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>
@@ -37,4 +37,7 @@
<!-- Intent key for package name values -->
<string name="config_helpIntentNameKey" translatable="false"></string>
-</resources>
+
+ <!-- The apps that need to be hided when they are disabled -->
+ <string-array name="config_hideWhenDisabled_packageNames"></string-array>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7d9cc53..972fc73 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -65,9 +65,9 @@
<!-- Summary for the remembered network but currently not in range. -->
<string name="wifi_not_in_range">Not in range</string>
<!-- Summary for the network but no internet connection was detected. -->
- <string name="wifi_no_internet_no_reconnect">No Internet Access Detected, won\'t automatically reconnect.</string>
+ <string name="wifi_no_internet_no_reconnect">Won\'t automatically connect</string>
<!-- Summary for the remembered network but no internet connection was detected. -->
- <string name="wifi_no_internet">No Internet Access.</string>
+ <string name="wifi_no_internet">No Internet access</string>
<!-- Summary for saved networks -->
<string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
@@ -582,11 +582,6 @@
<!-- UI debug setting: force right to left layout summary [CHAR LIMIT=100] -->
<string name="force_rtl_layout_all_locales_summary">Force screen layout direction to RTL for all locales</string>
- <!-- UI debug setting: show how CPU is being used? [CHAR LIMIT=25] -->
- <string name="show_cpu_usage">Show CPU usage</string>
- <!-- UI debug setting: show cpu usage summary [CHAR LIMIT=50] -->
- <string name="show_cpu_usage_summary">Screen overlay showing current CPU usage</string>
-
<!-- UI debug setting: force hardware acceleration to render apps [CHAR LIMIT=25] -->
<string name="force_hw_ui">Force GPU rendering</string>
<!-- UI debug setting: force hardware acceleration summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index a22a051..f0ec1078 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -47,6 +47,7 @@
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.R;
import java.io.File;
import java.text.Collator;
@@ -621,7 +622,7 @@
}
if (filter != null) {
- filter.init();
+ filter.init(mContext);
}
List<AppEntry> apps;
@@ -1280,6 +1281,9 @@
public interface AppFilter {
void init();
+ default void init(Context context) {
+ init();
+ }
boolean filterApp(AppEntry info);
}
@@ -1398,6 +1402,33 @@
}
};
+ public static final AppFilter FILTER_NOT_HIDE = new AppFilter() {
+ private String[] mHidePackageNames;
+
+ public void init(Context context) {
+ mHidePackageNames = context.getResources()
+ .getStringArray(R.array.config_hideWhenDisabled_packageNames);
+ }
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
+ if (!entry.info.enabled) {
+ return false;
+ } else if (entry.info.enabledSetting ==
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+
public static class VolumeFilter implements AppFilter {
private final String mVolumeUuid;
@@ -1425,6 +1456,12 @@
}
@Override
+ public void init(Context context) {
+ mFirstFilter.init(context);
+ mSecondFilter.init(context);
+ }
+
+ @Override
public void init() {
mFirstFilter.init();
mSecondFilter.init();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 4bcbea7..a332332 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -268,16 +268,16 @@
if (cachedDevice == null) {
Log.w(TAG, "CachedBluetoothDevice for device " + device +
" not found, calling readPairedDevices().");
- if (!readPairedDevices()) {
- Log.e(TAG, "Got bonding state changed for " + device +
- ", but we have no record of that device.");
- return;
+ if (readPairedDevices()) {
+ cachedDevice = mDeviceManager.findDevice(device);
}
- cachedDevice = mDeviceManager.findDevice(device);
+
if (cachedDevice == null) {
- Log.e(TAG, "Got bonding state changed for " + device +
- ", but device not added in cache.");
- return;
+ Log.w(TAG, "Got bonding state changed for " + device +
+ ", but we have no record of that device.");
+
+ cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+ dispatchDeviceAdded(cachedDevice);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a879d16f..52e686c9b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -102,9 +102,6 @@
private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;
- /** Auto-connect after pairing only if locally initiated. */
- private boolean mConnectAfterPairing;
-
/**
* Describes the current device and profile for logging.
*
@@ -300,7 +297,6 @@
return false;
}
- mConnectAfterPairing = true; // auto-connect after pairing
return true;
}
@@ -309,7 +305,7 @@
* slightly different for local vs. remote initiated pairing dialogs.
*/
boolean isUserInitiatedPairing() {
- return mConnectAfterPairing;
+ return mDevice.isBondingInitiatedLocally();
}
public void unpair() {
@@ -549,7 +545,6 @@
void onBondingStateChanged(int bondState) {
if (bondState == BluetoothDevice.BOND_NONE) {
mProfiles.clear();
- mConnectAfterPairing = false; // cancel auto-connect
setPhonebookPermissionChoice(ACCESS_UNKNOWN);
setMessagePermissionChoice(ACCESS_UNKNOWN);
setSimPermissionChoice(ACCESS_UNKNOWN);
@@ -562,10 +557,9 @@
if (bondState == BluetoothDevice.BOND_BONDED) {
if (mDevice.isBluetoothDock()) {
onBondingDockConnect();
- } else if (mConnectAfterPairing) {
+ } else if (mDevice.isBondingInitiatedLocally()) {
connect(false);
}
- mConnectAfterPairing = false;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 26e8303..857ca49 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -40,23 +40,47 @@
import java.util.Set;
import java.util.TimeZone;
+/**
+ * ZoneGetter is the utility class to get time zone and zone list, and both of them have display
+ * name in time zone. In this class, we will keep consistency about display names for all
+ * the methods.
+ *
+ * The display name chosen for each zone entry depends on whether the zone is one associated
+ * with the country of the user's chosen locale. For "local" zones we prefer the "long name"
+ * (e.g. "Europe/London" -> "British Summer Time" for people in the UK). For "non-local"
+ * zones we prefer the exemplar location (e.g. "Europe/London" -> "London" for English
+ * speakers from outside the UK). This heuristic is based on the fact that people are
+ * typically familiar with their local timezones and exemplar locations don't always match
+ * modern-day expectations for people living in the country covered. Large countries like
+ * China that mostly use a single timezone (olson id: "Asia/Shanghai") may not live near
+ * "Shanghai" and prefer the long name over the exemplar location. The only time we don't
+ * follow this policy for local zones is when Android supplies multiple olson IDs to choose
+ * from and the use of a zone's long name leads to ambiguity. For example, at the time of
+ * writing Android lists 5 olson ids for Australia which collapse to 2 different zone names
+ * in winter but 4 different zone names in summer. The ambiguity leads to the users
+ * selecting the wrong olson ids.
+ *
+ */
public class ZoneGetter {
private static final String TAG = "ZoneGetter";
- private static final String XMLTAG_TIMEZONE = "timezone";
-
public static final String KEY_ID = "id"; // value: String
public static final String KEY_DISPLAYNAME = "name"; // value: String
public static final String KEY_GMT = "gmt"; // value: String
public static final String KEY_OFFSET = "offset"; // value: int (Integer)
- private ZoneGetter() {}
+ private static final String XMLTAG_TIMEZONE = "timezone";
- public static String getTimeZoneOffsetAndName(TimeZone tz, Date now) {
- Locale locale = Locale.getDefault();
- String gmtString = getGmtOffsetString(locale, tz, now);
- TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
- String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
+ public static String getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) {
+ final Locale locale = Locale.getDefault();
+ final String gmtString = getGmtOffsetString(locale, tz, now);
+ final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+ final ZoneGetterData data = new ZoneGetterData(context);
+
+ final boolean useExemplarLocationForLocalNames =
+ shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
+ final String zoneNameString = getTimeZoneDisplayName(data, timeZoneNames,
+ useExemplarLocationForLocalNames, tz, tz.getID());
if (zoneNameString == null) {
return gmtString;
}
@@ -69,82 +93,20 @@
final Locale locale = Locale.getDefault();
final Date now = new Date();
final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
-
- // The display name chosen for each zone entry depends on whether the zone is one associated
- // with the country of the user's chosen locale. For "local" zones we prefer the "long name"
- // (e.g. "Europe/London" -> "British Summer Time" for people in the UK). For "non-local"
- // zones we prefer the exemplar location (e.g. "Europe/London" -> "London" for English
- // speakers from outside the UK). This heuristic is based on the fact that people are
- // typically familiar with their local timezones and exemplar locations don't always match
- // modern-day expectations for people living in the country covered. Large countries like
- // China that mostly use a single timezone (olson id: "Asia/Shanghai") may not live near
- // "Shanghai" and prefer the long name over the exemplar location. The only time we don't
- // follow this policy for local zones is when Android supplies multiple olson IDs to choose
- // from and the use of a zone's long name leads to ambiguity. For example, at the time of
- // writing Android lists 5 olson ids for Australia which collapse to 2 different zone names
- // in winter but 4 different zone names in summer. The ambiguity leads to the users
- // selecting the wrong olson ids.
-
- // Get the list of olson ids to display to the user.
- List<String> olsonIdsToDisplayList = readTimezonesToDisplay(context);
-
- // Store the information we are going to need more than once.
- final int zoneCount = olsonIdsToDisplayList.size();
- final String[] olsonIdsToDisplay = new String[zoneCount];
- final TimeZone[] timeZones = new TimeZone[zoneCount];
- final String[] gmtOffsetStrings = new String[zoneCount];
- for (int i = 0; i < zoneCount; i++) {
- String olsonId = olsonIdsToDisplayList.get(i);
- olsonIdsToDisplay[i] = olsonId;
- TimeZone tz = TimeZone.getTimeZone(olsonId);
- timeZones[i] = tz;
- gmtOffsetStrings[i] = getGmtOffsetString(locale, tz, now);
- }
-
- // Create a lookup of local zone IDs.
- Set<String> localZoneIds = new HashSet<String>();
- for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
- localZoneIds.add(olsonId);
- }
+ final ZoneGetterData data = new ZoneGetterData(context);
// Work out whether the display names we would show by default would be ambiguous.
- Set<String> localZoneNames = new HashSet<String>();
- boolean useExemplarLocationForLocalNames = false;
- for (int i = 0; i < zoneCount; i++) {
- String olsonId = olsonIdsToDisplay[i];
- if (localZoneIds.contains(olsonId)) {
- TimeZone tz = timeZones[i];
- String displayName = getZoneLongName(timeZoneNames, tz, now);
- if (displayName == null) {
- displayName = gmtOffsetStrings[i];
- }
- boolean nameIsUnique = localZoneNames.add(displayName);
- if (!nameIsUnique) {
- useExemplarLocationForLocalNames = true;
- break;
- }
- }
- }
+ final boolean useExemplarLocationForLocalNames =
+ shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
// Generate the list of zone entries to return.
List<Map<String, Object>> zones = new ArrayList<Map<String, Object>>();
- for (int i = 0; i < zoneCount; i++) {
- String olsonId = olsonIdsToDisplay[i];
- TimeZone tz = timeZones[i];
- String gmtOffsetString = gmtOffsetStrings[i];
+ for (int i = 0; i < data.zoneCount; i++) {
+ TimeZone tz = data.timeZones[i];
+ String gmtOffsetString = data.gmtOffsetStrings[i];
- boolean isLocalZoneId = localZoneIds.contains(olsonId);
- boolean preferLongName = isLocalZoneId && !useExemplarLocationForLocalNames;
- String displayName;
- if (preferLongName) {
- displayName = getZoneLongName(timeZoneNames, tz, now);
- } else {
- displayName = timeZoneNames.getExemplarLocationName(tz.getID());
- if (displayName == null || displayName.isEmpty()) {
- // getZoneExemplarLocation can return null. Fall back to the long name.
- displayName = getZoneLongName(timeZoneNames, tz, now);
- }
- }
+ String displayName = getTimeZoneDisplayName(data, timeZoneNames,
+ useExemplarLocationForLocalNames, tz, data.olsonIdsToDisplay[i]);
if (displayName == null || displayName.isEmpty()) {
displayName = gmtOffsetString;
}
@@ -198,28 +160,103 @@
return olsonIds;
}
+ private static boolean shouldUseExemplarLocationForLocalNames(ZoneGetterData data,
+ TimeZoneNames timeZoneNames) {
+ final Set<String> localZoneNames = new HashSet<String>();
+ final Date now = new Date();
+ for (int i = 0; i < data.zoneCount; i++) {
+ final String olsonId = data.olsonIdsToDisplay[i];
+ if (data.localZoneIds.contains(olsonId)) {
+ final TimeZone tz = data.timeZones[i];
+ String displayName = getZoneLongName(timeZoneNames, tz, now);
+ if (displayName == null) {
+ displayName = data.gmtOffsetStrings[i];
+ }
+ final boolean nameIsUnique = localZoneNames.add(displayName);
+ if (!nameIsUnique) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static String getTimeZoneDisplayName(ZoneGetterData data, TimeZoneNames timeZoneNames,
+ boolean useExemplarLocationForLocalNames, TimeZone tz, String olsonId) {
+ final Date now = new Date();
+ final boolean isLocalZoneId = data.localZoneIds.contains(olsonId);
+ final boolean preferLongName = isLocalZoneId && !useExemplarLocationForLocalNames;
+ String displayName;
+
+ if (preferLongName) {
+ displayName = getZoneLongName(timeZoneNames, tz, now);
+ } else {
+ displayName = timeZoneNames.getExemplarLocationName(tz.getID());
+ if (displayName == null || displayName.isEmpty()) {
+ // getZoneExemplarLocation can return null. Fall back to the long name.
+ displayName = getZoneLongName(timeZoneNames, tz, now);
+ }
+ }
+
+ return displayName;
+ }
+
/**
* Returns the long name for the timezone for the given locale at the time specified.
* Can return {@code null}.
*/
private static String getZoneLongName(TimeZoneNames names, TimeZone tz, Date now) {
- TimeZoneNames.NameType nameType =
+ final TimeZoneNames.NameType nameType =
tz.inDaylightTime(now) ? TimeZoneNames.NameType.LONG_DAYLIGHT
- : TimeZoneNames.NameType.LONG_STANDARD;
+ : TimeZoneNames.NameType.LONG_STANDARD;
return names.getDisplayName(tz.getID(), nameType, now.getTime());
}
private static String getGmtOffsetString(Locale locale, TimeZone tz, Date now) {
// Use SimpleDateFormat to format the GMT+00:00 string.
- SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ");
+ final SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ");
gmtFormatter.setTimeZone(tz);
String gmtString = gmtFormatter.format(now);
// Ensure that the "GMT+" stays with the "00:00" even if the digits are RTL.
- BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
boolean isRtl = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
gmtString = bidiFormatter.unicodeWrap(gmtString,
isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR);
return gmtString;
}
-}
+
+ private static final class ZoneGetterData {
+ public final String[] olsonIdsToDisplay;
+ public final String[] gmtOffsetStrings;
+ public final TimeZone[] timeZones;
+ public final Set<String> localZoneIds;
+ public final int zoneCount;
+
+ public ZoneGetterData(Context context) {
+ final Locale locale = Locale.getDefault();
+ final Date now = new Date();
+ final List<String> olsonIdsToDisplayList = readTimezonesToDisplay(context);
+
+ // Load all the data needed to display time zones
+ zoneCount = olsonIdsToDisplayList.size();
+ olsonIdsToDisplay = new String[zoneCount];
+ timeZones = new TimeZone[zoneCount];
+ gmtOffsetStrings = new String[zoneCount];
+ for (int i = 0; i < zoneCount; i++) {
+ final String olsonId = olsonIdsToDisplayList.get(i);
+ olsonIdsToDisplay[i] = olsonId;
+ final TimeZone tz = TimeZone.getTimeZone(olsonId);
+ timeZones[i] = tz;
+ gmtOffsetStrings[i] = getGmtOffsetString(locale, tz, now);
+ }
+
+ // Create a lookup of local zone IDs.
+ localZoneIds = new HashSet<String>();
+ for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
+ localZoneIds.add(olsonId);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
new file mode 100644
index 0000000..c1f5660
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -0,0 +1,36 @@
+/**
+ * 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.settingslib.drawer;
+
+public final class CategoryKey {
+
+ // Activities in this category shows up in Settings homepage.
+ public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";
+
+ // Top level category.
+ 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";
+ public static final String CATEGORY_STORAGE = "com.android.settings.category.ia.storage";
+ public static final String CATEGORY_SECURITY = "com.android.settings.category.ia.security";
+ public static final String CATEGORY_ACCOUNT = "com.android.settings.category.ia.accounts";
+ public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system";
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
new file mode 100644
index 0000000..a51ad76
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
@@ -0,0 +1,112 @@
+/**
+ * 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.settingslib.drawer;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class CategoryManager {
+
+ private static final String TAG = "CategoryManager";
+
+ private static CategoryManager sInstance;
+ private final InterestingConfigChanges mInterestingConfigChanges;
+
+ // Tile cache (key: <packageName, activityName>, value: tile)
+ private final Map<Pair<String, String>, Tile> mTileByComponentCache;
+
+ // Tile cache (key: category key, value: category)
+ private final Map<String, DashboardCategory> mCategoryByKeyMap;
+
+ private List<DashboardCategory> mCategories;
+
+ public static CategoryManager get(Context context) {
+ if (sInstance == null) {
+ sInstance = new CategoryManager(context);
+ }
+ return sInstance;
+ }
+
+ CategoryManager(Context context) {
+ mTileByComponentCache = new ArrayMap<>();
+ mCategoryByKeyMap = new ArrayMap<>();
+ mInterestingConfigChanges = new InterestingConfigChanges();
+ mInterestingConfigChanges.applyNewConfig(context.getResources());
+ }
+
+ public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
+ tryInitCategories(context);
+
+ return mCategoryByKeyMap.get(categoryKey);
+ }
+
+ public synchronized List<DashboardCategory> getCategories(Context context) {
+ tryInitCategories(context);
+ return mCategories;
+ }
+
+ public synchronized void reloadAllCategories(Context context) {
+ final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig(
+ context.getResources());
+ mCategories = null;
+ tryInitCategories(context, forceClearCache);
+ }
+
+ public synchronized void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) {
+ if (mCategories == null) {
+ Log.w(TAG, "Category is null, skipping blacklist update");
+ }
+ for (int i = 0; i < mCategories.size(); i++) {
+ DashboardCategory category = mCategories.get(i);
+ for (int j = 0; j < category.tiles.size(); j++) {
+ Tile tile = category.tiles.get(j);
+ if (tileBlacklist.contains(tile.intent.getComponent())) {
+ category.tiles.remove(j--);
+ }
+ }
+ }
+ }
+
+ private synchronized void tryInitCategories(Context context) {
+ // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
+ // happens.
+ tryInitCategories(context, false /* forceClearCache */);
+ }
+
+ private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
+ if (mCategories == null) {
+ if (forceClearCache) {
+ mTileByComponentCache.clear();
+ }
+ mCategoryByKeyMap.clear();
+ mCategories = TileUtils.getCategories(context, mTileByComponentCache,
+ false /* categoryDefinedInManifest */);
+ for (DashboardCategory category : mCategories) {
+ mCategoryByKeyMap.put(category.key, category);
+ }
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index 53be0e6..3fc999f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,15 +16,20 @@
package com.android.settingslib.drawer;
+import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class DashboardCategory implements Parcelable {
+ private static final String TAG = "DashboardCategory";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
/**
* Title of the category that is shown to the user.
*/
@@ -74,6 +79,22 @@
return tiles.get(n);
}
+ public boolean containsComponent(ComponentName component) {
+ for (Tile tile : tiles) {
+ if (TextUtils.equals(tile.intent.getComponent().getClassName(),
+ component.getClassName())) {
+ if (DEBUG) {
+ Log.d(TAG, "category " + key + "contains component" + component);
+ }
+ return true;
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "category " + key + " does not contain component" + component);
+ }
+ return false;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 05585e53e..bad7ba4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -33,7 +33,6 @@
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.support.v4.widget.DrawerLayout;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -64,12 +63,9 @@
public static final String EXTRA_SHOW_MENU = "show_drawer_menu";
- private static List<DashboardCategory> sDashboardCategories;
- private static HashMap<Pair<String, String>, Tile> sTileCache;
// Serves as a temporary list of tiles to ignore until we heard back from the PM that they
// are disabled.
private static ArraySet<ComponentName> sTileBlacklist = new ArraySet<>();
- private static InterestingConfigChanges sConfigTracker;
private final PackageReceiver mPackageReceiver = new PackageReceiver();
private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
@@ -80,6 +76,15 @@
private boolean mShowingMenu;
private UserManager mUserManager;
+ // Remove below after new IA
+ @Deprecated
+ private static List<DashboardCategory> sDashboardCategories;
+ @Deprecated
+ private static HashMap<Pair<String, String>, Tile> sTileCache;
+ @Deprecated
+ private static InterestingConfigChanges sConfigTracker;
+ // Remove above after new IA
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -105,7 +110,9 @@
mDrawerLayout = null;
return;
}
- getDashboardCategories();
+ if (!isDashboardFeatureEnabled()) {
+ getDashboardCategories();
+ }
setActionBar(toolbar);
mDrawerAdapter = new SettingsDrawerAdapter(this);
ListView listView = (ListView) findViewById(R.id.left_drawer);
@@ -144,7 +151,11 @@
filter.addDataScheme("package");
registerReceiver(mPackageReceiver, filter);
- new CategoriesUpdater().execute();
+ if (isDashboardFeatureEnabled()) {
+ new CategoriesUpdateTask().execute();
+ } else {
+ new CategoriesUpdater().execute();
+ }
}
final Intent intent = getIntent();
if (intent != null) {
@@ -173,23 +184,23 @@
if (componentName == null) {
return false;
}
- // Look for a tile that has the same component as incoming intent
- final List<DashboardCategory> categories = getDashboardCategories();
- for (DashboardCategory category : categories) {
- for (Tile tile : category.tiles) {
- if (TextUtils.equals(tile.intent.getComponent().getClassName(),
- componentName.getClassName())) {
- if (DEBUG) {
- Log.d(TAG, "intent is for top level tile: " + tile.title);
- }
+ if (isDashboardFeatureEnabled()) {
+ final DashboardCategory homepageCategories = CategoryManager.get(this)
+ .getTilesByCategory(this, CategoryKey.CATEGORY_HOMEPAGE);
+ return homepageCategories.containsComponent(componentName);
+ } else {
+ // Look for a tile that has the same component as incoming intent
+ final List<DashboardCategory> categories = getDashboardCategories();
+ for (DashboardCategory category : categories) {
+ if (category.containsComponent(componentName)) {
return true;
}
}
+ if (DEBUG) {
+ Log.d(TAG, "Intent is not for top level settings " + intent);
+ }
+ return false;
}
- if (DEBUG) {
- Log.d(TAG, "Intent is not for top level settings " + intent);
- }
- return false;
}
public void addCategoryListener(CategoryListener listener) {
@@ -255,7 +266,11 @@
return;
}
// TODO: Do this in the background with some loading.
- mDrawerAdapter.updateCategories();
+ if (isDashboardFeatureEnabled()) {
+ mDrawerAdapter.updateHomepageCategories();
+ } else {
+ mDrawerAdapter.updateCategories();
+ }
if (mDrawerAdapter.getCount() != 0) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
} else {
@@ -343,13 +358,6 @@
}
}
- public HashMap<Pair<String, String>, Tile> getTileCache() {
- if (sTileCache == null) {
- getDashboardCategories();
- }
- return sTileCache;
- }
-
public void onProfileTileOpen() {
finish();
}
@@ -368,7 +376,11 @@
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
- new CategoriesUpdater().execute();
+ if (isDashboardFeatureEnabled()) {
+ new CategoriesUpdateTask().execute();
+ } else {
+ new CategoriesUpdater().execute();
+ }
}
}
@@ -376,6 +388,10 @@
void onCategoriesChanged();
}
+ /**
+ * @deprecated remove after new IA
+ */
+ @Deprecated
private class CategoriesUpdater extends AsyncTask<Void, Void, List<DashboardCategory>> {
@Override
protected List<DashboardCategory> doInBackground(Void... params) {
@@ -408,10 +424,39 @@
}
}
+ private class CategoriesUpdateTask extends AsyncTask<Void, Void, Void> {
+
+ private final CategoryManager mCategoryManager;
+
+ public CategoriesUpdateTask() {
+ mCategoryManager = CategoryManager.get(SettingsDrawerActivity.this);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mCategoryManager.reloadAllCategories(SettingsDrawerActivity.this);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ mCategoryManager.updateCategoryFromBlacklist(sTileBlacklist);
+ onCategoriesChanged();
+ }
+ }
+
+ protected boolean isDashboardFeatureEnabled() {
+ return false;
+ }
+
private class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- new CategoriesUpdater().execute();
+ if (isDashboardFeatureEnabled()) {
+ new CategoriesUpdateTask().execute();
+ } else {
+ new CategoriesUpdater().execute();
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
index 1d6197a..602d135 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
@@ -37,6 +37,10 @@
mActivity = activity;
}
+ /**
+ * @deprecated Remove after new IA
+ */
+ @Deprecated
void updateCategories() {
List<DashboardCategory> categories = mActivity.getDashboardCategories();
mItems.clear();
@@ -64,6 +68,27 @@
notifyDataSetChanged();
}
+ public void updateHomepageCategories() {
+ final DashboardCategory category = CategoryManager.get(mActivity)
+ .getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE);
+ mItems.clear();
+ // Spacer.
+ mItems.add(null);
+ Item tile = new Item();
+ tile.label = mActivity.getString(R.string.home);
+ tile.icon = Icon.createWithResource(mActivity, R.drawable.home);
+ mItems.add(tile);
+ for (int j = 0; j < category.tiles.size(); j++) {
+ tile = new Item();
+ Tile dashboardTile = category.tiles.get(j);
+ tile.label = dashboardTile.title;
+ tile.icon = dashboardTile.icon;
+ tile.tile = dashboardTile;
+ mItems.add(tile);
+ }
+ notifyDataSetChanged();
+ }
+
public Tile getTile(int position) {
return mItems.get(position) != null ? mItems.get(position).tile : null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index e70cc29..ac10ca8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -113,8 +113,24 @@
private static final String SETTING_PKG = "com.android.settings";
+ /**
+ * Build a list of DashboardCategory. Each category must be defined in manifest.
+ * eg: .Settings$DeviceSettings
+ * @deprecated
+ */
+ @Deprecated
public static List<DashboardCategory> getCategories(Context context,
- HashMap<Pair<String, String>, Tile> cache) {
+ Map<Pair<String, String>, Tile> cache) {
+ return getCategories(context, cache, true /*categoryDefinedInManifest*/);
+ }
+
+ /**
+ * Build a list of DashboardCategory.
+ * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
+ * represent this category (eg: .Settings$DeviceSettings)
+ */
+ public static List<DashboardCategory> getCategories(Context context,
+ Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) {
final long startTime = System.currentTimeMillis();
boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
!= 0;
@@ -126,19 +142,20 @@
// 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);
}
}
+
HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
for (Tile tile : tiles) {
DashboardCategory category = categoryMap.get(tile.category);
if (category == null) {
- category = createCategory(context, tile.category);
+ category = createCategory(context, tile.category, categoryDefinedInManifest);
if (category == null) {
Log.w(LOG_TAG, "Couldn't find category " + tile.category);
continue;
@@ -157,9 +174,21 @@
return categories;
}
- private static DashboardCategory createCategory(Context context, String categoryKey) {
+ /**
+ * Create a new DashboardCategory from key.
+ *
+ * @param context Context to query intent
+ * @param categoryKey The category key
+ * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
+ * represent this category (eg: .Settings$DeviceSettings)
+ */
+ private static DashboardCategory createCategory(Context context, String categoryKey,
+ boolean categoryDefinedInManifest) {
DashboardCategory category = new DashboardCategory();
category.key = categoryKey;
+ if (!categoryDefinedInManifest) {
+ return category;
+ }
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
if (results.size() == 0) {
@@ -182,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,
@@ -204,6 +241,8 @@
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
+
+ // Load category
if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
&& categoryKey == null) {
Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
@@ -213,6 +252,7 @@
} else {
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
}
+
Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
activityInfo.name);
Tile tile = addedCache.get(key);
@@ -238,16 +278,6 @@
}
}
- private static DashboardCategory getCategory(List<DashboardCategory> target,
- String categoryKey) {
- for (DashboardCategory category : target) {
- if (categoryKey.equals(category.key)) {
- return category;
- }
- }
- return null;
- }
-
private static boolean updateTileData(Context context, Tile tile,
ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
if (applicationInfo.isSystemApp()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index a514ebb..c2161ae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -288,7 +288,7 @@
public boolean matches(WifiConfiguration config) {
if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
- return config.FQDN.equals(mConfig.providerFriendlyName);
+ return config.FQDN.equals(mConfig.FQDN);
} else {
return ssid.equals(removeDoubleQuotes(config.SSID))
&& security == getSecurity(config)
@@ -407,7 +407,11 @@
}
public DetailedState getDetailedState() {
- return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null;
+ if (mNetworkInfo != null) {
+ return mNetworkInfo.getDetailedState();
+ }
+ Log.w(TAG, "NetworkInfo is null, cannot return detailed state");
+ return null;
}
public String getSavedNetworkSummary() {
@@ -847,7 +851,10 @@
return context.getString(R.string.wifi_connected_no_internet);
}
}
-
+ if (state == null) {
+ Log.w(TAG, "state is null, returning empty summary");
+ return "";
+ }
String[] formats = context.getResources().getStringArray((ssid == null)
? R.array.wifi_status : R.array.wifi_status_with_ssid);
int index = state.ordinal();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index bfe8c07..77a45b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -484,7 +484,8 @@
mMainHandler.scheduleAPCopyingAndCloseWriteLock();
}
- private AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
+ @VisibleForTesting
+ AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
final int N = cache.size();
for (int i = 0; i < N; i++) {
if (cache.get(i).matches(result)) {
@@ -493,10 +494,13 @@
return ret;
}
}
- return new AccessPoint(mContext, result);
+ final AccessPoint accessPoint = new AccessPoint(mContext, result);
+ accessPoint.setListener(mAccessPointListenerAdapter);
+ return accessPoint;
}
- private AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
+ @VisibleForTesting
+ AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
final int N = cache.size();
for (int i = 0; i < N; i++) {
if (cache.get(i).matches(config)) {
@@ -505,7 +509,7 @@
return ret;
}
}
- AccessPoint accessPoint = new AccessPoint(mContext, config);
+ final AccessPoint accessPoint = new AccessPoint(mContext, config);
accessPoint.setListener(mAccessPointListenerAdapter);
return accessPoint;
}
diff --git a/packages/SettingsLib/tests/AndroidManifest.xml b/packages/SettingsLib/tests/AndroidManifest.xml
index 18bbbed..00b2164 100644
--- a/packages/SettingsLib/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/AndroidManifest.xml
@@ -20,6 +20,9 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
+ <uses-permission android:name="android.permission.SET_TIME_ZONE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.java
new file mode 100644
index 0000000..57e06dd
--- /dev/null
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/utils/ZoneGetterTest.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.settingslib.utils;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.*;
+import com.android.settingslib.datetime.ZoneGetter;
+
+import static junit.framework.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ZoneGetterTest {
+ private static final String TIME_ZONE_LONDON_ID = "Europe/London";
+ private static final String TIME_ZONE_LA_ID = "America/Los_Angeles";
+ private Locale mLocaleEnUs;
+ private Calendar mCalendar;
+
+ @Before
+ public void setUp() {
+ mLocaleEnUs = new Locale("en", "us");
+ Locale.setDefault(mLocaleEnUs);
+ mCalendar = new GregorianCalendar(2016, 9, 1);
+ }
+
+ @Test
+ public void getTimeZoneOffsetAndName_setLondon_returnLondon() {
+ // Check it will ends with 'London', not 'British Summer Time' or sth else
+ testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London");
+ }
+
+ @Test
+ public void getTimeZoneOffsetAndName_setLosAngeles_returnPacificDaylightTime() {
+ // Check it will ends with 'Pacific Daylight Time', not 'Los_Angeles'
+ testTimeZoneOffsetAndNameInner(TIME_ZONE_LA_ID, "Pacific Daylight Time");
+ }
+
+ private void testTimeZoneOffsetAndNameInner(String timeZoneId, String expectedName) {
+ final Context context = InstrumentationRegistry.getContext();
+ final TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
+
+ String timeZoneString = ZoneGetter.getTimeZoneOffsetAndName(context, timeZone,
+ mCalendar.getTime());
+
+ assertTrue(timeZoneString.endsWith(expectedName));
+ }
+
+}
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java
new file mode 100644
index 0000000..c650190
--- /dev/null
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.settingslib.wifi;
+
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WifiTrackerTest {
+
+ @Test
+ public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.level = 123;
+ scanResult.BSSID = "bssid-" + 111;
+ scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+ scanResult.capabilities = "";
+
+ WifiTracker tracker = new WifiTracker(
+ InstrumentationRegistry.getTargetContext(), null, true, true);
+
+ AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
+ assertTrue(result.mAccessPointListener != null);
+ }
+
+ @Test
+ public void testAccessPointListenerSetWhenLookingUpUsingWifiConfiguration() {
+ WifiConfiguration configuration = new WifiConfiguration();
+ configuration.SSID = "test123";
+ configuration.BSSID="bssid";
+ configuration.networkId = 123;
+ configuration.allowedKeyManagement = new BitSet();
+ configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+
+ WifiTracker tracker = new WifiTracker(
+ InstrumentationRegistry.getTargetContext(), null, true, true);
+
+ AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
+ assertTrue(result.mAccessPointListener != null);
+ }
+}
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 7338a9c..d55bb4f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1483,7 +1483,6 @@
Settings.Global.CALL_AUTO_RETRY,
Settings.Global.DEBUG_APP,
Settings.Global.WAIT_FOR_DEBUGGER,
- Settings.Global.SHOW_PROCESSES,
Settings.Global.ALWAYS_FINISH_ACTIVITIES,
};
String[] secureToGlobal = {
@@ -2663,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/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index dde71eb..b4bfb01 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -201,7 +201,12 @@
@After
public void tearDown() throws Exception {
Log.i(TAG, getName() + ".tearDown()");
- cancelExistingNotifications();
+ try {
+ cancelExistingNotifications();
+ } finally {
+ // Collapses just in case, so a failure here does not compromise tests on other classes.
+ mUiBot.collapseStatusBar();
+ }
}
@Test
@@ -362,7 +367,7 @@
detailsUi.assertName(NAME); // Sanity check
cancelFromNotification();
- mUiBot.closeNotifications();
+ mUiBot.collapseStatusBar();
assertDetailsUiClosed();
assertServiceNotRunning();
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index deab7da..e839765 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -62,7 +62,7 @@
return getObject(text);
}
- public void closeNotifications() throws Exception {
+ public void collapseStatusBar() throws Exception {
// TODO: mDevice should provide such method..
StatusBarManager sbm =
(StatusBarManager) mInstrumentation.getContext().getSystemService("statusbar");
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 71bfe85..ffddf02 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -2,10 +2,9 @@
include $(CLEAR_VARS)
-LOCAL_MODULE := SystemUI-proto-tags
+LOCAL_MODULE := SystemUI-proto
-LOCAL_SRC_FILES := $(call all-proto-files-under,src) \
- src/com/android/systemui/EventLogTags.logtags
+LOCAL_SRC_FILES := $(call all-proto-files-under,src)
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
@@ -33,7 +32,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
framework-protos \
- SystemUI-proto-tags
+ SystemUI-proto
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_JAVA_LIBRARIES += android.car
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 8ed1be5..4d59d57 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -219,19 +219,10 @@
</intent-filter>
</receiver>
- <service android:name=".LoadAverageService"
- android:exported="true" />
-
<service android:name=".ImageWallpaper"
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
- <receiver android:name=".BootReceiver" androidprv:systemUserOnly="true">
- <intent-filter android:priority="1000">
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- </intent-filter>
- </receiver>
-
<activity android:name=".tuner.TunerActivity"
android:enabled="false"
android:icon="@drawable/tuner"
@@ -247,6 +238,18 @@
android:value="com.android.settings.category.system" />
</activity>
+ <activity-alias android:name=".tuner.TunerSettingLink"
+ android:targetActivity=".tuner.TunerActivity"
+ android:enabled="false"
+ android:process=":tuner">
+ <intent-filter android:priority="1">
+ <action android:name="com.android.settings.action.EXTRA_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.category"
+ android:value="com.android.settings.category.ia.system" />
+ </activity-alias>
+
<activity-alias android:name=".DemoMode"
android:targetActivity=".tuner.TunerActivity"
android:icon="@drawable/tuner"
@@ -386,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"
@@ -397,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=""
@@ -406,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/IntentButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
new file mode 100644
index 0000000..1b8efa7
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+
+/**
+ * An Intent Button represents a triggerable element in SysUI that consists of an
+ * Icon and an intent to trigger when it is activated (clicked, swiped, etc.).
+ */
+public interface IntentButtonProvider extends Plugin {
+
+ public static final int VERSION = 1;
+
+ public IntentButton getIntentButton();
+
+ public interface IntentButton {
+ public static class IconState {
+ public boolean isVisible = true;
+ public CharSequence contentDescription = null;
+ public Drawable drawable;
+ }
+
+ public IconState getIcon();
+
+ public Intent getIntent();
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
index 495771a..75a5434 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -14,12 +14,10 @@
package com.android.systemui.plugins;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -33,12 +31,10 @@
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.system.PathClassLoader;
-
import java.util.ArrayList;
import java.util.List;
-public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver {
+public class PluginInstanceManager<T extends Plugin> {
private static final boolean DEBUG = false;
@@ -57,20 +53,21 @@
final PluginHandler mPluginHandler;
private final boolean isDebuggable;
private final PackageManager mPm;
- private final ClassLoaderFactory mClassLoaderFactory;
+ private final PluginManager mManager;
PluginInstanceManager(Context context, String action, PluginListener<T> listener,
- boolean allowMultiple, Looper looper, int version) {
+ boolean allowMultiple, Looper looper, int version, PluginManager manager) {
this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
- Build.IS_DEBUGGABLE, new ClassLoaderFactory());
+ manager, Build.IS_DEBUGGABLE);
}
@VisibleForTesting
PluginInstanceManager(Context context, PackageManager pm, String action,
PluginListener<T> listener, boolean allowMultiple, Looper looper, int version,
- boolean debuggable, ClassLoaderFactory classLoaderFactory) {
+ PluginManager manager, boolean debuggable) {
mMainHandler = new MainHandler(Looper.getMainLooper());
mPluginHandler = new PluginHandler(looper);
+ mManager = manager;
mContext = context;
mPm = pm;
mAction = action;
@@ -78,44 +75,29 @@
mAllowMultiple = allowMultiple;
mVersion = version;
isDebuggable = debuggable;
- mClassLoaderFactory = classLoaderFactory;
}
- public void startListening() {
+ public void loadAll() {
if (DEBUG) Log.d(TAG, "startListening");
mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mContext.registerReceiver(this, filter);
- filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiver(this, filter);
}
- public void stopListening() {
+ public void destroy() {
if (DEBUG) Log.d(TAG, "stopListening");
ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
for (PluginInfo plugin : plugins) {
mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
plugin.mPlugin).sendToTarget();
}
- mContext.unregisterReceiver(this);
}
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "onReceive " + intent);
- if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
- } else {
- Uri data = intent.getData();
- String pkgName = data.getEncodedSchemeSpecificPart();
- mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkgName).sendToTarget();
- if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
- mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkgName).sendToTarget();
- }
- }
+ public void onPackageRemoved(String pkg) {
+ mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget();
+ }
+
+ public void onPackageChange(String pkg) {
+ mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget();
+ mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkg).sendToTarget();
}
public boolean checkAndDisable(String className) {
@@ -132,7 +114,9 @@
public void disableAll() {
ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
- plugins.forEach(this::disable);
+ for (int i = 0; i < plugins.size(); i++) {
+ disable(plugins.get(i));
+ }
}
private void disable(PluginInfo info) {
@@ -179,12 +163,6 @@
}
}
- static class ClassLoaderFactory {
- public ClassLoader createClassLoader(String path, ClassLoader base) {
- return new PathClassLoader(path, base);
- }
- }
-
private class PluginHandler extends Handler {
private static final int QUERY_ALL = 1;
private static final int QUERY_PKG = 2;
@@ -279,8 +257,7 @@
return null;
}
// Create our own ClassLoader so we can use our own code as the parent.
- ClassLoader classLoader = mClassLoaderFactory.createClassLoader(info.sourceDir,
- getClass().getClassLoader());
+ ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName);
Context pluginContext = new PluginContextWrapper(
mContext.createApplicationContext(info, 0), classLoader);
Class<?> pluginClass = Class.forName(cls, true, classLoader);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
index 4bf6494..686b4d4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
@@ -14,7 +14,11 @@
package com.android.systemui.plugins;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
import android.os.Build;
import android.os.HandlerThread;
import android.os.Looper;
@@ -22,26 +26,31 @@
import com.android.internal.annotations.VisibleForTesting;
+import dalvik.system.PathClassLoader;
+
import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Map;
/**
* @see Plugin
*/
-public class PluginManager {
+public class PluginManager extends BroadcastReceiver {
private static PluginManager sInstance;
private final HandlerThread mBackgroundThread;
private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
= new ArrayMap<>();
+ private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
private final Context mContext;
private final PluginInstanceManagerFactory mFactory;
private final boolean isDebuggable;
private final PluginPrefs mPluginPrefs;
+ private ClassLoaderFilter mParentClassLoader;
private PluginManager(Context context) {
- this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
- Thread.getDefaultUncaughtExceptionHandler());
+ this(context, new PluginInstanceManagerFactory(),
+ Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
}
@VisibleForTesting
@@ -72,9 +81,12 @@
}
mPluginPrefs.addAction(action);
PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
- allowMultiple, mBackgroundThread.getLooper(), version);
- p.startListening();
+ allowMultiple, mBackgroundThread.getLooper(), version, this);
+ p.loadAll();
mPluginMap.put(listener, p);
+ if (mPluginMap.size() == 1) {
+ startListening();
+ }
}
public void removePluginListener(PluginListener<?> listener) {
@@ -83,7 +95,68 @@
return;
}
if (!mPluginMap.containsKey(listener)) return;
- mPluginMap.remove(listener).stopListening();
+ mPluginMap.remove(listener).destroy();
+ if (mPluginMap.size() == 0) {
+ stopListening();
+ }
+ }
+
+ private void startListening() {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(this, filter);
+ filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiver(this, filter);
+ }
+
+ private void stopListening() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.loadAll();
+ }
+ } else {
+ Uri data = intent.getData();
+ String pkg = data.getEncodedSchemeSpecificPart();
+ clearClassLoader(pkg);
+ if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.onPackageChange(pkg);
+ }
+ } else {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.onPackageRemoved(pkg);
+ }
+ }
+ }
+ }
+
+ public ClassLoader getClassLoader(String sourceDir, String pkg) {
+ if (mClassLoaders.containsKey(pkg)) {
+ return mClassLoaders.get(pkg);
+ }
+ ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
+ mClassLoaders.put(pkg, classLoader);
+ return classLoader;
+ }
+
+ private void clearClassLoader(String pkg) {
+ mClassLoaders.remove(pkg);
+ }
+
+ ClassLoader getParentClassLoader() {
+ if (mParentClassLoader == null) {
+ // Lazily load this so it doesn't have any effect on devices without plugins.
+ mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(),
+ "com.android.systemui.plugin");
+ }
+ return mParentClassLoader;
}
public static PluginManager getInstance(Context context) {
@@ -97,9 +170,29 @@
public static class PluginInstanceManagerFactory {
public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
- int version) {
+ int version, PluginManager manager) {
return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
- version);
+ version, manager);
+ }
+ }
+
+
+ // This allows plugins to include any libraries or copied code they want by only including
+ // classes from the plugin library.
+ private static class ClassLoaderFilter extends ClassLoader {
+ private final String mPackage;
+ private final ClassLoader mBase;
+
+ public ClassLoaderFilter(ClassLoader base, String pkg) {
+ super(ClassLoader.getSystemClassLoader());
+ mBase = base;
+ mPackage = pkg;
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
+ return mBase.loadClass(name);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java
index 3270587..a616369 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainer.java
@@ -31,7 +31,7 @@
// This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader
// change in incompatible ways.
- public static final int VERSION = 1;
+ public static final int VERSION = 2;
public QSContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -40,6 +40,7 @@
public abstract void setPanelView(HeightListener notificationPanelView);
public abstract BaseStatusBarHeader getHeader();
+ public abstract void hideImmediately();
public abstract int getQsMinExpansionHeight();
public abstract int getDesiredHeight();
public abstract void setHeightOverride(int desiredHeight);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
new file mode 100644
index 0000000..09879d8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -0,0 +1,51 @@
+/*
+ * 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.annotation.DrawableRes;
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.Plugin;
+
+public interface NavBarButtonProvider extends Plugin {
+
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_BUTTON";
+
+ public static final int VERSION = 1;
+
+ /**
+ * Returns a view in the nav bar. If the id is set "back", "home", "recent_apps", "menu",
+ * or "ime_switcher", it is expected to implement ButtonInterface.
+ */
+ public View createView(String spec, ViewGroup parent);
+
+ /**
+ * Interface for button actions.
+ */
+ interface ButtonInterface {
+ void setImageResource(@DrawableRes int resId);
+
+ void setImageDrawable(@Nullable Drawable drawable);
+
+ void abortCurrentGesture();
+
+ void setLandscape(boolean landscape);
+
+ void setCarMode(boolean carMode);
+ }
+}
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/ic_qs_signal_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
index f63dfb12..b78d3bf 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,17 +15,15 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
- android:fillColor="#4DFFFFFF"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
index 7fb423e..e055de7 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,20 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.7,20.0l2.0,0.0l0.0,2.0l-2.0,0.0z"/>
+ android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.7,10.0l2.0,0.0l0.0,8.1l-2.0,0.0z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M17.7,8.0l4.299999,0.0 0.0,-6.0 -20.0,20.0 15.700001,0.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M10.1,13.9l-8.1,8.1 8.1,0.0z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
index 3358d65..8a48817 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,20 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.900000,10.000000l-11.900000,12.000000 11.900000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
- android:fillColor="#4DFFFFFF"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
index 63838a9..39cc94c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,20 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,19.900000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,9.900000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16.700001,7.200000l-14.700001,14.700000 14.700001,0.000000z"/>
- <path
- android:pathData="M17.700001,7.900000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
- android:fillColor="#4DFFFFFF"/>
+ android:pathData="M14.1,14.1l2.9,0.0 0.0,-6.5 -15.0,15.0 12.1,0.0z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
index 76690cc..012e95e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,17 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M2.000000,22.000000l15.700001,0.000000 0.000000,-14.000000 4.299999,0.000000 0.000000,-6.000000z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
index 50c427e..e6f9292 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -14,17 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.200000,-1.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
index a2d11a0..d423ccb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -14,20 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.000000,22.000000l5.500000,-6.800000c-0.200000,-0.200000 -2.300000,-1.900000 -5.500000,-1.900000s-5.300000,1.800000 -5.500000,1.900000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
index f2043fc..1982130 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -14,20 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.000000,11.600000c-1.300000,-0.700000 -3.400000,-1.600000 -6.000000,-1.600000c-4.400000,0.000000 -7.300000,2.400000 -7.600000,2.700000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,11.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.800001,1.9 -1.800001,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
index b7a4f4c..b350111 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -14,20 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.000000,8.600000c-1.600000,-0.700000 -3.600000,-1.300000 -6.000000,-1.300000c-5.300000,0.000000 -8.900000,3.000000 -9.200000,3.200000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
index 35a9138..136a004 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -14,17 +14,14 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
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/drawable/stat_sys_signal_0.xml b/packages/SystemUI/res/drawable/stat_sys_signal_0.xml
index 643c4f9..8bc872a 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_0.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_0.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,17 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_1.xml b/packages/SystemUI/res/drawable/stat_sys_signal_1.xml
index 64781c3..8fa7630 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_1.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_1.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,20 +15,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.7,20.0l2.0,0.0l0.0,2.0l-2.0,0.0z"/>
+ android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.7,10.0l2.0,0.0l0.0,8.1l-2.0,0.0z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M17.7,8.0l4.299999,0.0 0.0,-6.0 -20.0,20.0 15.700001,0.0z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M10.1,13.9l-8.1,8.1 8.1,0.0z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_2.xml b/packages/SystemUI/res/drawable/stat_sys_signal_2.xml
index eb2be08..2a660a3 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_2.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_2.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,20 +15,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.900000,10.000000l-11.900000,12.000000 11.900000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_3.xml b/packages/SystemUI/res/drawable/stat_sys_signal_3.xml
index 22afad0..9e0a433 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_3.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_3.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,20 +15,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,19.900000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,9.900000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M16.700001,7.200000l-14.700001,14.700000 14.700001,0.000000z"/>
- <path
- android:pathData="M17.700001,7.900000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
+ <path
+ android:pathData="M14.1,14.1l2.9,0.0 0.0,-6.5 -15.0,15.0 12.1,0.0z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_4.xml b/packages/SystemUI/res/drawable/stat_sys_signal_4.xml
index d1e866d..01f6703 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_4.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_4.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,18 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
-
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M2.000000,22.000000l15.700001,0.000000 0.000000,-14.000000 4.299999,0.000000 0.000000,-6.000000z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
index 7f1b715e..2de2e36 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,16 +15,13 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.200000,-1.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
index acd89be..144a7c1 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,19 +15,16 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.000000,22.000000l5.500000,-6.800000c-0.200000,-0.200000 -2.300000,-1.900000 -5.500000,-1.900000s-5.300000,1.800000 -5.500000,1.900000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
index f33b25c..6b7f712 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,19 +15,16 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M19.000000,11.600000c-1.300000,-0.700000 -3.400000,-1.600000 -6.000000,-1.600000c-4.400000,0.000000 -7.300000,2.400000 -7.600000,2.700000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,11.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.800001,1.9 -1.800001,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
index 09d2e50..d34b4de 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,19 +15,16 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M19.000000,8.600000c-1.600000,-0.700000 -3.600000,-1.300000 -6.000000,-1.300000c-5.300000,0.000000 -8.900000,3.000000 -9.200000,3.200000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
index fb1f584..5701356 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
@@ -1,7 +1,7 @@
<!--
-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");
+ 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
@@ -15,16 +15,13 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/layout/car_navigation_bar.xml b/packages/SystemUI/res/layout/car_navigation_bar.xml
index f7f673d..999dbac 100644
--- a/packages/SystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/SystemUI/res/layout/car_navigation_bar.xml
@@ -21,6 +21,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
+ android:gravity="center"
android:background="@drawable/system_bar_background">
<!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed
@@ -28,7 +29,7 @@
-->
<LinearLayout
android:layout_height="match_parent"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/car_navigation_bar_width"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false"
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 ec77d77..1131b2a 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-data is laat wag"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Sellulêre data is onderbreek"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data is onderbreek"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Omdat die gestelde dataperk bereik is, het die toestel datagebruik vir die res van hierdie siklus onderbreek.\n\nAs dit hervat word, kan dit tot heffings deur jou diensverskaffer lei."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Jy het die datalimiet wat jy gestel het, bereik. Jy gebruik nie meer sellulêre data nie.\n\nAs jy voortgaan, kan heffings vir datagebruik geld."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Hervat"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Geen internetverbinding nie"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi gekoppel"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> waarskuwing"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Werkmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Aandbeligting"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Aandbeligting is aan, tik om af te skakel"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Aandbeligting is af, tik om aan te skakel"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Geen onlangse items nie"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Jy het alles toegemaak"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Programinligting"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Kitsboodskappe"</string>
+ <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 9e00b7a..0dc05e6 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4ጂ ውሂብ ላፍታ ቆሟል"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"የተንቀሳቃሽ ስልክ ውሂብ ላፍታ ቆሟል"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ውሂብ ላፍታ ቆሟል"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"የእርስዎ የተዋቀረው የውሂብ ገደብ ላይ ስለተደረሰ፣ የዚህን ዑደት አጠቃቀም ለማስታወስ መሣሪያው ላፍታ ቆሟል።\n\nከቆመበት ማስቀጠሉ ከእርስዎ የአገልግሎት አቅራቢ ክፍያን ሊያስጠይቅዎት ይችላል።"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"እርስዎ ያስቀመጡት የውሂብ ገደብ ላይ ተደርሷል። ከእንግዲህ ተንቀሳቃሽ ውሂብ እየተጠቀሙ አይደለም ያሉት።\n\nከቆመበት ከቀጠሉ የውሂብ አጠቃቀም ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ።"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ከቆመበት ቀጥል"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ምንም በይነመረብ ተያያዥ የለም።"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ተያይዟል"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"የ<xliff:g id="DATA_LIMIT">%s</xliff:g> ማስጠንቀቂያ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"የሥራ ሁነታ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"የምሽት ብርሃን"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"የምሽት ብርሃን በርቷል፣ ለማጥፋት መታ ያድርጉ"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"የምሽት ብርሃን ጠፍቷል፣ ለማብራት መታ ያድርጉ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ሁሉንም ነገር አጽድተዋል"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"የመተግበሪያ መረጃ"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"ፈጣን መልዕክት"</string>
+ <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 93c46472..755438e 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"تم إيقاف بيانات شبكة الجيل الرابع مؤقتًا"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"تم إيقاف بيانات شبكة الجوّال مؤقتًا"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"تم إيقاف البيانات مؤقتًا"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"نظرًا لأنك بلغت الحد الأقصى المحدد للبيانات، فقد أوقف الجهاز استخدام البيانات مؤقتًا في بقية هذه الدورة.\n\nومن الممكن أن يؤدي الاستئناف إلى تحصيل رسوم من قِبل مشغِّل شبكة الجوّال."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"تم الوصول إلى حد البيانات الذي عيَّنته، ولم يعد بإمكانك استخدام بيانات شبكة الجوّال.\n\nعند الاستئناف، قد يتم تحصيل رسوم مقابل استخدام البيانات."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"استئناف"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"لا يوجد اتصال إنترنت"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi متصل"</string>
@@ -327,8 +327,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"تحذير <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"وضع العمل"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"إضاءة ليلية"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"الإضاءة الليلية قيد العمل، انقر لإيقافها."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"الإضاءة الليلية قيد الإيقاف، انقر لتشغيلها."</string>
<string name="recents_empty_message" msgid="808480104164008572">"ليست هناك عناصر تم استخدامها مؤخرًا"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"لقد محوتَ كل شيء"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"معلومات التطبيق"</string>
@@ -572,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"الرسائل الفورية"</string>
+ <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 1096108..87b4433 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G məlumatlarına fasilə verildi"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobil məlumatlara fasilə verildi"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Məlumatlara fasilə verildi"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Məlumatlar dəsti limitinizi keçdiyiniz üçün cihaz bu dövrənin qalan hissəsi üçün məlumatların istifadəsinə fasilə verib.\n\nDavam etmək operaturunuzdan xərclərə səbəb ola bilər."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Daha limitini keçdiniz. Artıq mobil data istifadə etmirsiniz.\n\nDavam etsəniz, data istifadəsi üçün ödəniş tətbiq oluna bilər."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Davam et"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"İnternet bağlantısı yoxdur"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi qoşulub"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> xəbərdarlığı"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"İş rejimi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Gecə işığı"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Gecə işığı aktivdir, deaktiv etmək üçün tıklayın"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Gecə işığı deaktivdir, aktiv etmək üçün tıklayın"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Son elementlər yoxdur"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Hərşeyi təmizlədiniz"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Tətbiq haqqında"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 d184130..e173890 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -238,7 +238,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G podaci su pauzirani"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilni podaci su pauzirani"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Podaci su pauzirani"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Zbog toga što ste dostigli podešeno ograničenje za podatke, uređaj je pauzirao korišćenje podataka tokom ostatka ovog ciklusa.\n\nAko nastavite, mobilni operater može da vam naplati dodatne troškove."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Ograničenje potrošnje podataka koje ste podesili je dostignuto. Više ne koristite mobilne podatke.\n\nAko nastavite, možda će biti naplaćeni troškovi za potrošnju podataka."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Nastavi"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nema internet veze"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi je povezan"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozorenje za <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Režim rada"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noćno svetlo"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Noćno svetlo je uključeno, dodirnite da biste ga isključili"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Noćno svetlo je isključeno, dodirnite da biste ga uključili"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nema nedavnih stavki"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Obrisali ste sve"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Razmena trenutnih poruka"</string>
+ <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 cc71580..207e223 100644
--- a/packages/SystemUI/res/values-be-rBY/strings.xml
+++ b/packages/SystemUI/res/values-be-rBY/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Перадача даных 4G прыпынена"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Мабільная перадача даных прыпынена"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Перадача даных прыпынена"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Быў дасягнуты ліміт перадачы даных, таму прылада прыпыніла перадачу даных на астатнюю частку гэтага цыкла.\n\nУзнаўленне перадачы можа прывесці да спагнання платы вашым аператарам."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Ліміт даных, які вы задалі, быў дасягнуты. Вы больш не выкарыстоўваеце сотавую перадачу даных.\n\nКалі вы ўзновіце карыстанне, можа спаганяцца плата за выкарыстанне трафіка."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Узнавіць"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Няма падключэння да Iнтэрнэту"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi падключаны"</string>
@@ -325,8 +325,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Папярэджанне: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Рэжым працы"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Начная падсветка"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"\"Начная падсветка\" ўключана; дакраніцеся, каб выключыць"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"\"Начная падсветка\" выключана; дакраніцеся, каб уключыць"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Няма нядаўніх элементаў"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Вы ачысцілі усё"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Звесткі аб праграме"</string>
@@ -570,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Iмгненныя паведамленнi"</string>
+ <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 42ac038..16c94d7 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Данните от 4G са поставени на пауза"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Мобилните данни са поставени на пауза"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Данните са поставени на пауза"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Тъй като зададеното от вас ограничение за данни бе достигнато, устройството постави преноса им на пауза за остатъка от този цикъл.\n\nВъзобновяването може да доведе до таксуване от оператора ви."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Достигнахте зададеното от вас ограничение за данните. Вече не използвате мобилната мрежа.\n\nАко възобновите връзката с нея, може да бъдете таксувани за пренос на данни."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Възобновяване"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Няма връзка с интернет"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: Има връзка"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Предупреждение: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Работен режим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Нощно осветление"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Функцията за нощно осветление е включена. Докоснете, за да я изключите"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Функцията за нощно осветление е изключена. Докоснете, за да я включите"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Няма скорошни елементи"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Изчистихте всичко"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Информация за приложението"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Незабавни съобщения"</string>
+ <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 1467eea..feaa453 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G ডেটা বিরতি দেওয়া হয়েছে"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"সেলুলার ডেটা বিরতি দেওয়া হয়েছে"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ডেট বিরতি দেওয়া হয়েছে"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"আপনার সেট ডেটার সীমা অবধি পৌঁছনোর কারনে ডিভাইস এই চক্রের অবশিষ্টাংশের জন্য ডেটা ব্যবহারে বিরতি দেওয়া হয়েছে৷ \n\nপুনরায় চালু করা হলে পরিষেবা প্রদানকারীর দ্বারা চার্জের করা হতে পারে৷"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"আপনার সেটা করা ডেটা সীমা ছাড়িয়ে গেছে৷ আপনি আর সেলুলার ডেটা ব্যবহার করতে পারবেন না৷\n\nআপনি যদি আবার ব্যবহার করতে শুরু করেন তাহলে ডেটা ব্যবহারের জন্য চার্জ লাগতে পারে৷"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"পুনঃসূচনা করুন"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"কোনো ইন্টারনেট সংযোগ নেই"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"ওয়াই-ফাই সংযুক্ত হয়েছে"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> সতর্কতা"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"কাজের মোড"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"নাইট লাইট"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"নাইট লাইট চালু আছে, বন্ধ করতে আলতো চাপুন"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"নাইট লাইট বন্ধ আছে, চালু করতে আলতো চাপুন"</string>
<string name="recents_empty_message" msgid="808480104164008572">"কোনো সাম্প্রতিক আইটেম নেই"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"আপনি সবকিছু সাফ করেছেন"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"অ্যাপ্লিকেশানের তথ্য"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 349098a..f361b93 100644
--- a/packages/SystemUI/res/values-bs-rBA/strings.xml
+++ b/packages/SystemUI/res/values-bs-rBA/strings.xml
@@ -238,7 +238,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G prijenos podataka je pauzirano"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilni podaci su pauzirani"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Prijenos podataka je pauziran"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Dostigli ste postavljeno ograničenje prijenosa podataka pa je uređaj zaustavio prijenos podataka za preostali dio ovog ciklusa.\n\nAko nastavite, operater vam može naplatiti dodatne troškove."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Dostigli ste ograničenje za prijenos podataka koje ste postavili. Više ne koristite mobilne podatke.\n\nUkoliko nastavite koristiti mobilne podatke, mogući su troškovi za prijenos podataka."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Nastavi"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nema internet veze"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi veza aktivna"</string>
@@ -313,7 +313,7 @@
<string name="quick_settings_notifications_label" msgid="4818156442169154523">"Obavještenja"</string>
<string name="quick_settings_flashlight_label" msgid="2133093497691661546">"Svjetiljka"</string>
<string name="quick_settings_cellular_detail_title" msgid="8575062783675171695">"Mobilni podaci"</string>
- <string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Korištenje podataka"</string>
+ <string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Prijenos podataka"</string>
<string name="quick_settings_cellular_detail_remaining_data" msgid="722715415543541249">"Preostala količina podataka"</string>
<string name="quick_settings_cellular_detail_over_limit" msgid="967669665390990427">"Prekoračeno"</string>
<string name="quick_settings_cellular_detail_data_used" msgid="1476810587475761478">"Iskorišteno <xliff:g id="DATA_USED">%s</xliff:g>"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozorenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Poslovni režim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noćno svjetlo"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Noćno svjetlo je uključeno, dodirnite da isključite"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Noćno svjetlo je isključeno, dodirnite da uključite"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nema nedavnih stavki"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Sve ste obrisali"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
@@ -568,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 b764bf6..8bedecd 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Les dades 4G estan aturades"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Les dades mòbils estan aturades"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Les dades estan aturades"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Com que has arribat al límit de dades establert, s\'ha aturat l\'ús de dades del dispositiu per a la resta d\'aquest cicle.\n\nSi el reprens, l\'operador de telefonia mòbil pot aplicar càrrecs."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"S\'ha assolit el límit de dades establert. Ja no estàs utilitzant dades mòbils. \n\n Si reprens l\'ús de les dades, es poden aplicar càrrecs."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Reprèn"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"No hi ha connexió a Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: connectada"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertiment: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode de feina"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Llum nocturna"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"La llum nocturna està encesa; toca aquí per apagar-la"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"La llum nocturna està apagada; toca aquí per encendre-la"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No hi ha cap element recent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Ho has esborrat tot"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informació de l\'aplicació"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"MI"</string>
+ <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 a829fe7..0ff0f93 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Data 4G jsou pozastavena"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilní data jsou pozastavena"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data jsou pozastavena"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Protože jste dosáhli nastaveného limitu dat, zařízení využití dat pro zbytek tohoto cyklu pozastavilo.\n\nObnovení může vést k poplatkům od operátora."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Byl dosažen limit dat. Používání mobilních dat bylo vypnuto.\n\nPokud jej obnovíte, mohou vám být účtovány poplatky za využití dat."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Pokračovat"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Žádné přip. k internetu"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: připojeno"</string>
@@ -325,8 +325,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozornění při <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Pracovní režim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noční režim"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Noční režim je zapnut, klepnutím jej vypnete"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Noční režim je vypnut, klepnutím jej zapnete"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Žádné nedávné položky"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vše je vymazáno"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informace o aplikaci"</string>
@@ -570,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Chat"</string>
+ <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 64046e7..b112405 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-data er sat på pause"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobildata er sat på pause"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data er sat på pause"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Eftersom din fastsatte datagrænse blev nået, har enheden sat dataforbruget på pause i den resterende del af cyklussen.\n\nHvis du genaktiverer dataforbruget, kan det medføre gebyrer fra dit mobilselskab."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Du har nået den angivne datagrænse. Du vil ikke længere bruge mobildata.\n\nHvis du fortsætter, vil du muligvis blive opkrævet betaling for dit dataforbrug."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Genoptag"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ingen internetforb."</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi er forbundet"</string>
@@ -303,7 +303,7 @@
<string name="quick_settings_inversion_label" msgid="8790919884718619648">"Byt om på farver"</string>
<string name="quick_settings_color_space_label" msgid="853443689745584770">"Farvekorrigeringstilstand"</string>
<string name="quick_settings_more_settings" msgid="326112621462813682">"Flere indstillinger"</string>
- <string name="quick_settings_done" msgid="3402999958839153376">"Udført"</string>
+ <string name="quick_settings_done" msgid="3402999958839153376">"Udfør"</string>
<string name="quick_settings_connected" msgid="1722253542984847487">"Tilsluttet"</string>
<string name="quick_settings_connecting" msgid="47623027419264404">"Opretter forbindelse…"</string>
<string name="quick_settings_tethering_label" msgid="7153452060448575549">"Netdeling"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advarsel ved <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbejdstilstand"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nattelys"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nattelys er tændt. Tryk for at slukke"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nattelys er slukket. Tryk for at tænde"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ingen nye elementer"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du har ryddet alt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Oplysninger om applikationen"</string>
@@ -478,7 +476,7 @@
<string name="zen_alarm_warning" msgid="444533119582244293">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="alarm_template" msgid="3980063409350522735">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="alarm_template_far" msgid="4242179982586714810">"på <xliff:g id="WHEN">%1$s</xliff:g>"</string>
- <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Hurtigindstillinger <xliff:g id="TITLE">%s</xliff:g>."</string>
+ <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Hurtige indstillinger <xliff:g id="TITLE">%s</xliff:g>."</string>
<string name="accessibility_status_bar_hotspot" msgid="4099381329956402865">"Hotspot"</string>
<string name="accessibility_managed_profile" msgid="6613641363112584120">"Arbejdsprofil"</string>
<string name="tuner_warning_title" msgid="7094689930793031682">"Sjovt for nogle, men ikke for alle"</string>
@@ -520,7 +518,7 @@
<string name="notification_importance_high" msgid="1729480727023990427">"Se altid smugkig. Ingen afbrydelse af fuld skærm."</string>
<string name="notification_importance_max" msgid="2508384624461849111">"Se altid smugkig, og tillad afbrydelse af fuld skærm."</string>
<string name="notification_more_settings" msgid="816306283396553571">"Flere indstillinger"</string>
- <string name="notification_done" msgid="5279426047273930175">"Færdig"</string>
+ <string name="notification_done" msgid="5279426047273930175">"Udfør"</string>
<string name="notification_gear_accessibility" msgid="94429150213089611">"Kontrolelementer til underretninger for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="battery_panel_title" msgid="7944156115535366613">"Batteriforbrug"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterisparefunktionen er ikke tilgængelig under opladning"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Chat"</string>
+ <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 134e41f..9721362 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-Daten pausiert"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilfunkdaten pausiert"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Daten pausiert"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Da dein festgelegtes Datenlimit erreicht wurde, hat das Gerät die Datennutzung für den Rest dieses Zeitraums pausiert.\n\nWenn du die Nutzung fortsetzt, entstehen möglicherweise Kosten bei deinem Mobilfunkanbieter."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Das von dir festgelegte Datenlimit wurde erreicht. Die mobile Datennutzung wurde deaktiviert.\n\nWenn du weiterhin mobile Daten nutzt, können Gebühren anfallen."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Fortsetzen"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Keine Internetverbindung"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"WLAN verbunden"</string>
@@ -270,7 +270,7 @@
<string name="quick_settings_bluetooth_label" msgid="6304190285170721401">"Bluetooth"</string>
<string name="quick_settings_bluetooth_multiple_devices_label" msgid="3912245565613684735">"Bluetooth (<xliff:g id="NUMBER">%d</xliff:g> Geräte)"</string>
<string name="quick_settings_bluetooth_off_label" msgid="8159652146149219937">"Bluetooth aus"</string>
- <string name="quick_settings_bluetooth_detail_empty_text" msgid="4910015762433302860">"Keine Pairing-Geräte verfügbar"</string>
+ <string name="quick_settings_bluetooth_detail_empty_text" msgid="4910015762433302860">"Keine gekoppelten Geräte verfügbar"</string>
<string name="quick_settings_brightness_label" msgid="6968372297018755815">"Helligkeit"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="7305323031808150099">"Automatisch drehen"</string>
<string name="accessibility_quick_settings_rotation" msgid="4231661040698488779">"Bildschirm automatisch drehen"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Warnung für <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbeitsmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nachtlicht"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nachtlicht an, zum Deaktivieren tippen"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nachtlicht aus, zum Aktivieren tippen"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Keine kürzlich verwendeten Elemente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du hast alles gelöscht"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"App-Info"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 b108543..276b856 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -163,7 +163,7 @@
<string name="accessibility_battery_level_charging" msgid="1147587904439319646">"Φόρτιση μπαταρίας, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> τοις εκατό."</string>
<string name="accessibility_settings_button" msgid="799583911231893380">"Ρυθμίσεις συστήματος."</string>
<string name="accessibility_notifications_button" msgid="4498000369779421892">"Ειδοποιήσεις."</string>
- <string name="accessibility_remove_notification" msgid="3603099514902182350">"Εκκαθάριση ειδοποίησης."</string>
+ <string name="accessibility_remove_notification" msgid="3603099514902182350">"Διαγραφή ειδοποίησης."</string>
<string name="accessibility_gps_enabled" msgid="3511469499240123019">"Το GPS ενεργοποιήθηκε."</string>
<string name="accessibility_gps_acquiring" msgid="8959333351058967158">"Προσδιορισμός GPS."</string>
<string name="accessibility_tty_enabled" msgid="4613200365379426561">"Το TeleTypewriter ενεργοποιήθηκε."</string>
@@ -237,14 +237,14 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Τα δεδομένα 4G τέθηκαν σε παύση"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Τα δεδομένα κινητής τηλεφωνίας τέθηκαν σε παύση"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Τα δεδομένα τέθηκαν σε παύση"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Επειδή συμπληρώθηκε το όριο των δεδομένων που ορίστηκε για τη συσκευή σας, η χρήση δεδομένων τέθηκε σε παύση για το υπόλοιπο αυτού του κύκλου.\n\nΗ εκ νέου ενεργοποίησή τους ενδέχεται να επιφέρει χρεώσεις από την εταιρεία κινητής τηλεφωνίας σας."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Το όριο δεδομένων που ορίσατε έχει εξαντληθεί. Δεν χρησιμοποιείτε πλέον δεδομένα κινητής τηλεφωνίας.\n\nΑν συνεχίσετε, ενδέχεται να ισχύσουν χρεώσεις για τη χρήση δεδομένων."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Συνέχιση"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Χωρ. σύνδ. στο Διαδ."</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi συνδεδεμένο"</string>
<string name="gps_notification_searching_text" msgid="8574247005642736060">"Αναζήτηση για GPS"</string>
<string name="gps_notification_found_text" msgid="4619274244146446464">"Ρύθμιση τοποθεσίας με GPS"</string>
<string name="accessibility_location_active" msgid="2427290146138169014">"Τα αιτήματα τοποθεσίας έχουν ενεργοποιηθεί"</string>
- <string name="accessibility_clear_all" msgid="5235938559247164925">"Εκκαθάριση όλων των ειδοποιήσεων."</string>
+ <string name="accessibility_clear_all" msgid="5235938559247164925">"Διαγραφή όλων των ειδοποιήσεων."</string>
<string name="notification_group_overflow_indicator" msgid="1863231301642314183">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string>
<plurals name="notification_group_overflow_description" formatted="false" msgid="4579313201268495404">
<item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> επιπλέον ειδοποιήσεις εντός της ομάδας.</item>
@@ -319,10 +319,8 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Προειδοποίηση για <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Λειτουργία εργασίας"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Νυχτερινός φωτισμός"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Ο Νυχτερινός φωτισμός είναι ενεργοποιημένος. Πατήστε για απενεργοποίηση."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Ο Νυχτερινός φωτισμός είναι απενεργοποιημένος. Πατήστε για ενεργοποίηση."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
- <string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Έχει γίνει εκκαθάριση όλων των στοιχείων"</string>
+ <string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Έχει γίνει διαγραφή όλων των στοιχείων"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Πληροφορίες εφαρμογής"</string>
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"καρφίτσωμα οθόνης"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"αναζήτηση"</string>
@@ -437,7 +435,7 @@
<string name="quick_settings_reset_confirmation_message" msgid="2235970126803317374">"Θα εμφανιστεί ξανά την επόμενη φορά που θα το ενεργοποιήσετε στις ρυθμίσεις."</string>
<string name="quick_settings_reset_confirmation_button" msgid="2660339101868367515">"Απόκρυψη"</string>
<string name="volumeui_prompt_message" msgid="918680947433389110">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> θέλει να γίνει το παράθυρο διαλόγου ελέγχου έντασης"</string>
- <string name="volumeui_prompt_allow" msgid="7954396902482228786">"Να επιτραπεί"</string>
+ <string name="volumeui_prompt_allow" msgid="7954396902482228786">"Να επιτρέπεται"</string>
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Απόρριψη"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> αποτελεί το παράθυρο διαλόγου ελέγχου έντασης"</string>
<string name="volumeui_notification_text" msgid="8819536904234337445">"Πατήστε για να επαναφέρετε την αρχική μορφή της εικόνας."</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Άμεσα μηνύματα (ΙΜ)"</string>
+ <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 776e0430..ca2e5b3 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G data is paused"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobile data is paused"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data is paused"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Because your set data limit was reached, the device has paused data usage for the remainder of this cycle.\n\nResuming may lead to charges from your operator."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"The data limit you have set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Resume"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"No Internet connection"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi connected"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Night Light on, tap to turn off"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Night Light off, tap to turn on"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No recent items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"You\'ve cleared everything"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 776e0430..ca2e5b3 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G data is paused"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobile data is paused"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data is paused"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Because your set data limit was reached, the device has paused data usage for the remainder of this cycle.\n\nResuming may lead to charges from your operator."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"The data limit you have set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Resume"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"No Internet connection"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi connected"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Night Light on, tap to turn off"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Night Light off, tap to turn on"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No recent items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"You\'ve cleared everything"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 776e0430..ca2e5b3 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G data is paused"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobile data is paused"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data is paused"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Because your set data limit was reached, the device has paused data usage for the remainder of this cycle.\n\nResuming may lead to charges from your operator."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"The data limit you have set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Resume"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"No Internet connection"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi connected"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Night Light on, tap to turn off"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Night Light off, tap to turn on"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No recent items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"You\'ve cleared everything"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 abaeafe..81696f6 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Datos 4G pausados"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Datos móviles pausados"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Datos pausados"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Debido que se alcanzó el límite de datos establecido, el dispositivo pausó el uso de datos para el resto de este ciclo.\n\nLa reanudación podría tener como resultado cargos del proveedor."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Se alcanzó el límite de datos que estableciste. Ya no estás usando datos móviles.\n\nSi reanudas el uso de datos, es posible que se apliquen cargos."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Reanudar"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sin conexión a Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertencia de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabajo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz nocturna"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Presiona para desactivar la Luz nocturna"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Presiona para activar la Luz nocturna"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No hay elementos recientes"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Todo borrado"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Información de la aplicación"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 bfb37b6..aa9578a 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -232,14 +232,14 @@
<string name="accessibility_quick_settings_work_mode_on" msgid="7650588553988014341">"Modo de trabajo activado."</string>
<string name="accessibility_quick_settings_work_mode_changed_off" msgid="5605534876107300711">"Modo de trabajo desactivado."</string>
<string name="accessibility_quick_settings_work_mode_changed_on" msgid="249840330756998612">"Modo de trabajo activado."</string>
- <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Economizador de Datos desactivado."</string>
- <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Economizador de Datos activado."</string>
+ <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Ahorro de datos desactivado."</string>
+ <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Ahorro de datos activado."</string>
<string name="accessibility_brightness" msgid="8003681285547803095">"Brillo de la pantalla"</string>
<string name="data_usage_disabled_dialog_3g_title" msgid="5281770593459841889">"Datos 2G-3G pausados"</string>
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Datos 4G pausados"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Datos móviles pausados"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Datos pausados"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Has alcanzado el límite de datos establecido, por lo que el dispositivo ha pausado el uso de datos para el resto de este ciclo.\n\nSi lo reanudas, el operador puede facturar cargos."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Se ha alcanzado el límite de datos establecido. Ya no estás utilizando datos móviles.\n\nSi vuelves a activar el uso de datos, es posible que se apliquen cargos."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Reanudar"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sin conexión a Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Con conexión Wi-Fi"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertencia de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabajo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz nocturna"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Luz nocturna activada; toca aquí para desactivarla"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Luz nocturna desactivada; toca aquí para activarla"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No hay elementos recientes"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Has rechazado todo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Información de la aplicación"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"MI"</string>
+ <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>
@@ -579,9 +577,9 @@
<string name="headset" msgid="4534219457597457353">"Auriculares"</string>
<string name="accessibility_status_bar_headphones" msgid="9156307120060559989">"Auriculares conectados"</string>
<string name="accessibility_status_bar_headset" msgid="8666419213072449202">"Auriculares conectados"</string>
- <string name="data_saver" msgid="5037565123367048522">"Economizador de Datos"</string>
- <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Economizador de Datos activado"</string>
- <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Economizador de Datos desactivado"</string>
+ <string name="data_saver" msgid="5037565123367048522">"Ahorro de datos"</string>
+ <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Ahorro de datos activado"</string>
+ <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Ahorro de datos desactivado"</string>
<string name="switch_bar_on" msgid="1142437840752794229">"Sí"</string>
<string name="switch_bar_off" msgid="8803270596930432874">"No"</string>
<string name="nav_bar" msgid="1993221402773877607">"Barra de navegación"</string>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index ac55422..ab81af5 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G andmekasutus on peatatud"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobiilse andmeside kasutus on peatatud"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Andmekasutus on peatatud"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Kuna jõudsite andmemahu määratud piirini, peatas seade andmekasutuse ülejäänud tsükliks.\n\nJätkamisel võivad lisanduda operaatoritasud."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Olete jõudnud enda määratud andmemahupiiranguni. Te ei kasuta enam mobiilset andmesidet.\n\nKui jätkate, võivad rakenduda andmekasutustasud."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Jätka"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Interneti-ühendus puudub"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"WiFi on ühendatud"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> hoiatus"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Töörežiim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Öövalgus"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Öövalgus on sees, puudutage väljalülitamiseks"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Öövalgus on väljas, puudutage sisselülitamiseks"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Hiljutisi üksusi pole"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Olete kõik ära kustutanud"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Rakenduste teave"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM (kiirsuhtlus)"</string>
+ <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 e0912e5..3617074 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G datuen erabilera eten da"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Sare mugikorreko datuen erabilera eten da"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Datuen erabilera eten da"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Zehaztuta duzun datuen erabilera-mugara iritsi zarenez, gailuak datuen erabilera eten du zikloa amaitzen den arte.\n\nDatuak erabiltzen jarraitzen baduzu, gastu gehiago ordaindu beharko dizkiozu agian operadoreari."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Gainditu egin da ezarri duzun datu-muga. Datu-konexioa erabiltzeari utzi diozu.\n\nBerriro hasten bazara erabiltzen, baliteke datuen erabileraren kostua ordaindu behar izatea."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Jarraitu erabiltzen"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ez duzu Interneteko konexiorik"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi konektatuta"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Abisua: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Lan modua"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Gaueko argia"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Aktibatuta dago gaueko argia. Sakatu desaktibatzeko."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Desaktibatuta dago gaueko argia. Sakatu aktibatzeko."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ez dago azkenaldi honetako ezer"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Dena garbitu duzu"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Aplikazioaren informazioa"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Istanteko mezularitza"</string>
+ <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 df8ba8c..48f0b954 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"داده 4G موقتاً متوقف شده است"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"داده شبکه همراه موقتاً متوقف شده است"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"داده موقتاً متوقف شده است"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"چون به محدودیت داده تنظیم شده رسیدهاید، دستگاه مصرف داده را برای باقیمانده این دوره موقتاً متوقف کرده است.\n\nاگر ادامه دهید شاید موجب کسر هزینه از طرف شرکت مخابراتی شما شود."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"به حداکثر محدودیت دادهای که تنظیم کردید رسیدید. دیگر از داده شبکه تلفن همراه استفاده نمیکنید.\n\nدر صورت ازسرگیری، ممکن است مصرف داده هزینههایی دربرداشته باشد."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"از سرگیری"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی ندارید"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi متصل شد"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"هشدار <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"حالت کار"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"نور شب"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"نور شب روشن است، برای خاموشکردن آن ضربه بزنید"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"نور شب خاموش است، برای روشنشدن آن ضربه بزنید"</string>
<string name="recents_empty_message" msgid="808480104164008572">"بدون موارد اخیر"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"همهچیز را پاک کردهاید"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"اطلاعات برنامه"</string>
@@ -483,7 +481,7 @@
<string name="accessibility_managed_profile" msgid="6613641363112584120">"نمایه کاری"</string>
<string name="tuner_warning_title" msgid="7094689930793031682">"برای بعضی افراد سرگرمکننده است اما نه برای همه"</string>
<string name="tuner_warning" msgid="8730648121973575701">"«تنظیمکننده واسط کاربری سیستم» روشهای بیشتری برای تنظیم دقیق و سفارشی کردن واسط کاربری Android در اختیار شما قرار میدهد. ممکن است این ویژگیهای آزمایشی تغییر کنند، خراب شوند یا در نسخههای آینده جود نداشته باشند. با احتیاط ادامه دهید."</string>
- <string name="tuner_persistent_warning" msgid="8597333795565621795">"ممکن است این ویژگیهای آزمایشی تغییر کنند، خراب شوند یا در نسخههای آینده وجود نداشته باشند. با احتیاط ادامه دهید."</string>
+ <string name="tuner_persistent_warning" msgid="8597333795565621795">"ممکن است این قابلیتهای آزمایشی تغییر کنند، خراب شوند یا در نسخههای آینده وجود نداشته باشد. بااحتیاط ادامه دهید."</string>
<string name="got_it" msgid="2239653834387972602">"متوجه شدم"</string>
<string name="tuner_toast" msgid="603429811084428439">"تبریک میگوییم! «تنظیمکننده واسط کاربری سیستم» به «تنظیمات» اضافه شد"</string>
<string name="remove_from_settings" msgid="8389591916603406378">"حذف از تنظیمات"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"پیام فوری"</string>
+ <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 d157472..5e1ca4b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-tiedonsiirto keskeytettiin"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobiilitiedonsiirto keskeytettiin"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Tiedonsiirto keskeytettiin"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Määrittämäsi tiedonsiirtorajoitus saavutettiin, ja laite keskeytti tiedonsiirron tämän kauden loppuajaksi.\n\nOperaattorisi voi veloittaa sinulta lisämaksun, jos jatkat tiedonsiirtoa."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Määrittämäsi datankäyttöraja on täynnä. Mobiilidata poistettiin käytöstä.\n\nOperaattorisi voi veloittaa sinua, jos jatkat mobiilidatan käyttöä."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Jatka"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ei internetyhteyttä"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi yhdistetty"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> – varoitus"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Työtila"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Yövalo"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Yövalo on käytössä. Poista se käytöstä koskettamalla."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Yövalo ei ole käytössä. Ota se käyttöön koskettamalla."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ei viimeaikaisia kohteita"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Kaikki on hoidettu."</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Sovellustiedot"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Pikaviesti"</string>
+ <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 465de96..765e0aa 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Données 4G désactivées"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Données cellulaires désactivées"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Données désactivées"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Vous avez atteint le quota de données maximal. La consommation des données a donc été interrompue pour la fin de la période de facturation en cours.\n\nSi vous réactivez les données, votre fournisseur de services risque de vous facturer des frais supplémentaires."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"La limite de données que vous avez définie a été atteinte. Vous n\'utilisez plus les données cellulaires.\n\nSi vous rétablissez la connexion de données cellulaires, des frais peuvent s\'appliquer."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Reprendre"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Aucune connexion Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Connecté au Wi-Fi"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avertissement : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode Travail"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Éclairage nocturne"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Le mode Éclairage nocturne est activé. Touchez ici pour le désactiver."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Le mode Éclairage nocturne est désactivé. Touchez ici pour l\'activer."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Aucun élément récent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vous avez tout effacé"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Détails de l\'application"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"MI"</string>
+ <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 3ed8977..e66d342 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Données 4G désactivées"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Données mobiles désactivées"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Données désactivées"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Vous avez atteint le quota de données maximal. La consommation des données a donc été interrompue pour la fin de la période de facturation en cours.\n\nSi vous réactivez les données, votre opérateur risque de vous facturer des frais supplémentaires."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"La limite de consommation des données que vous avez définie a été atteinte. Vous n\'utilisez plus les données mobiles.\n\nSi vous les réactivez, des frais pourront être facturés."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Réactiver"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Aucune connexion Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Connecté au Wi-Fi"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avertissement : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode Travail"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Éclairage nocturne"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Éclairage nocturne activé, appuyer pour désactiver"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Éclairage nocturne désactivé, appuyer pour activer"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Aucun élément récent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vous avez tout effacé."</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Infos application"</string>
@@ -447,7 +445,7 @@
<string-array name="volume_stream_titles">
<item msgid="5841843895402729630">"Appeler"</item>
<item msgid="5997713001067658559">"Système"</item>
- <item msgid="7858983209929864160">"Faire sonner"</item>
+ <item msgid="7858983209929864160">"Sonnerie"</item>
<item msgid="1850038478268896762">"Multimédia"</item>
<item msgid="8265110906352372092">"Alarme"</item>
<item msgid="5339394737636839168"></item>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Messagerie instantanée"</string>
+ <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 dafe79d..5ccf4a0 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Os datos 4G están en pausa"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Os datos de móbiles están en pausa"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Os datos están en pausa"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Como acadaches o límite de datos definido, o dispositivo puxo en pausa o uso de datos para o resto do ciclo.\n\nSe retomas o uso, poden aplicarse cargos do operador."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Alcanzouse o límite de datos establecido e xa non utilizas datos móbiles.\n\nSe continúas, é posible que se apliquen cargos por uso de datos."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Retomar"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sen Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectada"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertencia <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de traballo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz nocturna"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"A función Luz nocturna está activada. Toca para desactivala"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"A función Luz nocturna está desactivada. Toca para activala"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Non hai elementos recentes"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Borraches todo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Información da aplicación"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"MI"</string>
+ <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 b230bac..84f9105 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G ડેટા થોભાવ્યો છે"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"સેલ્યુલર ડેટા થોભાવ્યો છે"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ડેટા થોભાવ્યો છે"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"તમે સેટ કરેલ ડેટા મર્યાદા સુધી પહોંચી ગયા હોવાથી, ઉપકરણે આ ચક્રના શેષ માટે ડેટા વપરાશ થોભાવ્યો છે.\n\nફરીથી શરૂ કરવું તમારા કેરીઅર તરફથી શુલ્ક તરફ દોરી શકે છે."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"તમારા દ્વારા સેટ કરેલ ડેટા મર્યાદા પર તમે પહોંચી ગયાં છો. તમે હવે સેલ્યુલર ડેટાનો ઉપયોગ કરી રહ્યાં નથી.\n\nજો તમે ફરી શરૂ કરો છો, તો ડેટા વપરાશ માટે શુલ્ક લાગુ થઈ શકે છે."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ફરી શરૂ કરો"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"કોઈ ઇન્ટરનેટ કનેક્શન નથી"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi કનેક્ટ કર્યું"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ચેતવણી"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"કાર્ય મોડ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"રાત્રિ પ્રકાશ"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"રાત્રિ પ્રકાશ ચાલુ છે, બંધ કરવા માટે ટપ કરો"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"રાત્રિ પ્રકાશ બંધ છે, ચાલુ કરવા માટે ટૅપ કરો"</string>
<string name="recents_empty_message" msgid="808480104164008572">"કોઇ તાજેતરની આઇટમ્સ નથી"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"તમે બધું સાફ કર્યું"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ઍપ્લિકેશન માહિતી"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 c781403..89b5a6f 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G डेटा रोक दिया गया है"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"सेल्युलर डेटा रोक दिया गया है"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"डेटा रोक दिया गया है"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"चूंकि आपके निर्धारित डेटा की सीमा, सीमा पर पहुंच गई थी, इसलिए डिवाइस ने इस चक्र के रिमाइंडर के लिए डेटा उपयोग को रोक दिया है.\n\nफिर से शुरू करने से आपके वाहक की ओर से शुल्क लगाया जाता है."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"आपकी सेट की हुई डेटा सीमा समाप्त हो गई है. अब आप सेल्युलर डेटा का उपयोग नहीं कर रहे हैं.\n\nयदि आप फिर से शुरू करते हैं, तो डेटा उपयोग के लिए शुल्क लग सकता है."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"फिर से शुरू करें"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"कोई इंटरनेट कनेक्शन नहीं"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"वाई-फ़ाई कनेक्ट किया गया"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> चेतावनी"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"कार्य मोड"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"नाइट लाइट"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"नाइट लाइट चालू है, बंद करने के लिए टैप करें"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"नाइट लाइट बंद है, चालू करने के लिए टैप करें"</string>
<string name="recents_empty_message" msgid="808480104164008572">"हाल ही का कोई आइटम नहीं"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"आपने सब कुछ साफ़ कर दिया है"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"एप्लिकेशन जानकारी"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 1de9f78..3c5812f 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -238,7 +238,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G podaci pauzirani"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilni podaci pauzirani"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Podaci su pauzirani"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Budući da je dosegnuto postavljeno ograničenje podataka, uređaj je pauzirao upotrebu podataka za preostali dio ovog ciklusa.\n\nMobilni operater može naplatiti daljnju upotrebu."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Dosegnuto je vaše ograničenje podataka. Više ne upotrebljavate mobilne podatke.\n\nAko nastavite, moguća je naplata za potrošnju podataka."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Nastavi"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nema internetske veze"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi povezan"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozorenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Način rada"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noćno svjetlo"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Noćno je svjetlo uključeno, dodirnite da biste ga isključili"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Noćno je svjetlo isključeno, dodirnite da biste ga uključili"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nema nedavnih stavki"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Izbrisali ste sve"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Izravna poruka"</string>
+ <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 6d699ad..f83ba28 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"A 4G adatforgalom szünetel"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"A mobilhálózati adatforgalom szünetel"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Az adatforgalom szünetel"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Mivel elérte a beállított adatkorlátot, az eszköz a ciklus fennmaradó részére felfüggesztette az adathasználatot.\n\nHa mégis használja az adatkapcsolatot, akkor szolgáltatója többletköltséget számíthat fel."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Elérte a beállított adatkorlátot. A továbbiakban nem használ mobiladat-forgalmat.\n\nHa a folytatást választja, szolgáltatója adathasználati díjat számíthat fel."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Folytatás"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nincs internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi csatlakoztatva"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Figyelem! <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Munka mód"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Éjszakai fény"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Éjszakai fény bekapcsolva, koppintson a kikapcsoláshoz"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Éjszakai fény kikapcsolva, koppintson a bekapcsoláshoz"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nincsenek mostanában használt elemek"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Mindent törölt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Az alkalmazás adatai"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Azonnali üzenetküldés"</string>
+ <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 e08ec46..4ce22e4 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4Գ տվյալների օգտագործումը դադարեցված է"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Բջջային տվյալների օգտագործումը դադարեցված է"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Տվյալների օգտագործումը դադարեցված է"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Քանի որ ձեր սահմանված տվյալների սահմանաչափը սպառվել է, սարքն այլևս չի օգտագործի տվյալները այս ցիկլի մնացած ընթացքում:\n\nԵթե վերսկսեք, հնարավոր է կիրառվեն գանձումներ ձեր օպերատորի կողմից:"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Տվյալների օգտագործման համար նշված սահմանաչափը լրացել է: Դուք բջջային տվյալներ այլևս չեք օգտագործում:\n\nԵթե վերսկսեք բջջային տվյալների օգտագործումը, դրա համար կարող են վճարներ գանձվել:"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Վերսկսել"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ինտերնետ կապ չկա"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi-ը միացված է"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> զգուշացում"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Աշխատանքային ռեժիմ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Գիշերային լույս"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Գիշերային լույսը միացված է, հպեք՝ անջատելու համար"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Գիշերային լույսն անջատված է, հպեք՝ միացնելու համար"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Վերջին տարրեր չկան"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Դուք ջնջել եք ամենը"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Հավելվածի մասին"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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>
@@ -643,7 +641,7 @@
<string name="accessibility_quick_settings_settings" msgid="6132460890024942157">"Բացել կարգավորումները:"</string>
<string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Բացել արագ կարգավորումները:"</string>
<string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Փակել արագ կարգավորումները:"</string>
- <string name="accessibility_quick_settings_alarm_set" msgid="1863000242431528676">"Զարթուցիչը կարգավորված է:"</string>
+ <string name="accessibility_quick_settings_alarm_set" msgid="1863000242431528676">"Զարթուցիչը դրված է:"</string>
<string name="accessibility_quick_settings_user" msgid="1567445362870421770">"Մուտք է գործել որպես <xliff:g id="ID_1">%s</xliff:g>"</string>
<string name="accessibility_quick_settings_no_internet" msgid="31890692343084075">"Ինտերնետ կապ չկա:"</string>
<string name="accessibility_quick_settings_open_details" msgid="4230931801728005194">"Բացել մանրամասները:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index de4b68d..4efffc56 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Data 4G dijeda"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Data seluler dijeda"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data dijeda"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Karena batas data yang disetel telah tercapai, perangkat telah menjeda penggunaan data selama sisa waktu siklus ini.\n\nMelanjutkan dapat mengakibatkan tagihan dari operator."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Batas data yang disetel telah tercapai. Anda tidak menggunakan data seluler lagi.\n\nJika Anda melanjutkan, biaya penggunaan data mungkin berlaku."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Lanjutkan"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Tidak ada sambungan internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi tersambung"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Peringatan <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode kerja"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Cahaya Malam"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Cahaya Malam hidup, ketuk untuk mematikan"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Cahaya Malam mati, ketuk untuk menyalakan"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Tidak ada item baru-baru ini"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Anda sudah menghapus semua"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Info Aplikasi"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 9b1e139..4875888 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Slökkt er á 4G-gögnum"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Slökkt er á farsímagögnum"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Slökkt er á gagnanotkun"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Þar sem gagnahámarkinu var náð hefur tækið slökkt á gagnanotkun það sem eftir er af þessu tímabili.\n\nEf þú heldur áfram kann það að leiða til kostnaðar frá símafyrirtækinu."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Gagnamörkunum sem þú stilltir hefur verið náð. Þú ert ekki lengur að nota farsímagögn.\n\nEf þú heldur áfram gætu gjöld fyrir gagnanotkun átt við."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Halda áfram"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Engin nettenging"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi tengt"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> viðvörun"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Vinnustilling"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Næturljós"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Kveikt á næturljósi, ýttu til að slökkva"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Slökkt á næturljósi, ýttu til að kveikja"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Engin nýleg atriði"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Þú hefur hreinsað allt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Forritsupplýsingar"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Spjall"</string>
+ <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 f207e43..98a24248 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Dati 4G sospesi"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Dati cellulari sospesi"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Dati sospesi"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Hai raggiunto il tuo limite di dati, pertanto sul dispositivo è stato sospeso l\'utilizzo di dati per la parte rimanente del ciclo.\n\nSe riprendi a utilizzare i dati, l\'operatore potrebbe addebitarti costi."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"È stato raggiunto il limite di dati impostato. La rete dati è stata disattivata.\n\nSe la riattivi, potrebbero essere applicati costi per l\'utilizzo dei dati."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Riprendi"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nessuna connessione"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi connesso"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avviso <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modalità Lavoro"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luminosità notturna"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Luminosità notturna attiva, tocca per disattivarla"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Luminosità notturna disattivata, tocca per attivarla"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nessun elemento recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Hai cancellato tutto"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informazioni sull\'applicazione"</string>
@@ -346,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>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Chat"</string>
+ <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 6462ae5..ce73705 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"השימוש בנתוני 4G מושהה"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"השימוש בנתונים סלולריים מושהה"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"השימוש בנתונים מושהה"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"מכיוון שהגעת למגבלת הנתונים שהגדרת, המכשיר השהה את השימוש בנתונים עד סוף התקופה.\n\nאם תמשיך, אתה עשוי לקבל חיובים מהספק."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"הגעת למגבלת הנתונים שהגדרת. אתה כבר לא משתמש בנתונים סלולריים.\n\nאם תמשיך, ייתכנו חיובים לשימוש בנתונים."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"המשך"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"אין חיבור לאינטרנט"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi מחובר"</string>
@@ -323,8 +323,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"אזהרה - <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"מצב עבודה"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"תאורת לילה"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"תאורת לילה פועלת, הקש כדי לכבות"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"תאורת לילה כבויה, הקש כדי להפעיל"</string>
<string name="recents_empty_message" msgid="808480104164008572">"אין פריטים אחרונים"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"מחקת הכול"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"מידע על האפליקציה"</string>
@@ -568,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"שליחת הודעות מיידיות"</string>
+ <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 fd59bc5..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>
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4Gデータは一時停止中です"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"モバイルデータは一時停止中です"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"データの一時停止"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"設定されたデータの上限に達したため、このサイクルの終了までこの端末でのデータの利用を一時停止しました。\n\n再開すると、携帯通信会社から課金される可能性があります。"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"設定されたデータの上限に達しているため、モバイルデータの使用を停止しました。\n\n再開すると、携帯通信会社からデータ使用量に応じた通信料を課金される可能性があります。"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"再開"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"インターネット未接続"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi接続済み"</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>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"警告: 上限は<xliff:g id="DATA_LIMIT">%s</xliff:g>です"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work モード"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"読書灯"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"読書灯 ON: タップすると OFF になります"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"読書灯 OFF: タップすると ON になります"</string>
<string name="recents_empty_message" msgid="808480104164008572">"最近のタスクはありません"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"すべてのタスクを消去しました"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"アプリ情報"</string>
@@ -566,14 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 28e37ca..a151b8f 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G მონაცემები შეჩერებულია"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"ფიჭური მონაცემები შეჩერებულია"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"მონაცემები შეჩერებულია"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"რადგან თქვენი მონაცემების ლიმიტი ამოწურულია, მოწყობილობამ შეაჭერა მონაცემების გამოყენება დარჩენილი ციკლისათვის. \n\n შეჯამაბ შეიძლება გამოიწვიოს თქვენს პროვაიდერთან დამატებითი ხარჯები."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"მიღწეულია მონაცემთა მოხმარების თქვენ მიერ მითითებული ლიმიტი. ამიტომ, მობილური ინტერნეტის გამოყენება აღარ ხდება.\n\nგანახლების შემთხვევაში, შეიძლება მობილური ინტერნეტის საფასურის გადახდა მოგიწიოთ."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"გაგრძელება"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ინტერნეტ კავშირი არ არის"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi დაკავშირებულია"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> გაფრთხილება"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"სამსახურის რეჟიმი"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ღამის განათება"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"ღამის განათება ჩართულია, შეეხეთ გამოსართავად"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"ღამის განათება გამორთულია, შეეხეთ ჩასართავად"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ყველაფერი გასუფთავდა"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"აპლიკაციის შესახებ"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 ad25a1c..6c0c967 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G деректері кідіртілді"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Ұялы деректер кідіртілді"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Деректер кідіртілді"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Орнатылған деректер шегіне жеткендіктен, құрылғы осы циклдың қалған бөлігі бойы деректерді пайдалануды кідіртті.\n\nЖалғастыру оператор ақыларына әкелуі мүмкін."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Белгіленген деректер шегіне жеттіңіз. Ұялы деректер енді пайдаланылмайды.\n\nЕгер жалғастырсаңыз, деректер трафигі үшін ақы алынуы мүмкін."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Жалғастыру"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Интернет байланысы жоқ"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi қосулы"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> туралы ескерту"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Жұмыс режимі"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Түнгі жарық"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Түнгі жарық қосулы, өшіру үшін оны түртіңіз"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Түнгі жарық өшірулі, қосу үшін оны түртіңіз"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Жақындағы элементтер жоқ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Сіз барлығын өшірдіңіз"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Қолданба туралы ақпарат"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 76a8f59..d3e97cc 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"ទិន្នន័យ 4G ត្រូវបានផ្អាក"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"ទិន្នន័យចល័តត្រូវបានផ្អាក"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ទិន្នន័យត្រូវបានផ្អាក"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"ដោយសារទិន្នន័យរបស់អ្នកបានឈានដល់កំណត់ ឧបករណ៍នេះបានផ្អាកការប្រើប្រាស់ទិន្នន័យសម្រាប់ការរំលឹកនៃវគ្គនេះ។\n\nការបន្តប្រើប្រាស់អាចនាំឲ្យមានការគិតប្រាក់ពីក្រុមហ៊ុនផ្តល់សេវាកម្ម។"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"បានឈានដល់កម្រិតទិន្នន័យដែលអ្នកបានកំណត់ហើយ។ ឥឡូវអ្នកមិនប្រើទិន្នន័យចល័តទៀតទេ។\n\nអាចនឹងគិតថ្លៃលើការប្រើទិន្នន័យ ប្រសិនបើអ្នកបន្តប្រើឡើងវិញ។"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"បន្ត"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"គ្មានការតភ្ជាប់អ៊ីនធឺណិត"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"បានភ្ជាប់វ៉ាយហ្វាយ"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ការព្រមាន"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"របៀបការងារ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ពន្លឺពេលយប់"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"ពន្លឺពេលយប់បើកហើយ ប៉ះដើម្បីបិទ"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"ពន្លឺពេលយប់បិទហើយ ប៉ះដើម្បីបើក"</string>
<string name="recents_empty_message" msgid="808480104164008572">"មិនមានធាតុថ្មីៗទេ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"អ្នកបានជម្រះអ្វីៗទាំងអស់"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ព័ត៌មានកម្មវិធី"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 69e3bdb..cbd71f9 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -84,7 +84,7 @@
<string name="accessibility_home" msgid="8217216074895377641">"ಮುಖಪುಟ"</string>
<string name="accessibility_menu" msgid="316839303324695949">"ಮೆನು"</string>
<string name="accessibility_recent" msgid="5208608566793607626">"ಸಮಗ್ರ ನೋಟ"</string>
- <string name="accessibility_search_light" msgid="1103867596330271848">"ಹುಡುಕು"</string>
+ <string name="accessibility_search_light" msgid="1103867596330271848">"ಹುಡುಕಿ"</string>
<string name="accessibility_camera_button" msgid="8064671582820358152">"ಕ್ಯಾಮರಾ"</string>
<string name="accessibility_phone_button" msgid="6738112589538563574">"ಫೋನ್"</string>
<string name="accessibility_voice_assist_button" msgid="487611083884852965">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
@@ -96,7 +96,7 @@
<string name="voice_assist_label" msgid="3956854378310019854">"ಧ್ವನಿ ಸಹಾಯಕವನ್ನು ತೆರೆ"</string>
<string name="camera_label" msgid="7261107956054836961">"ಕ್ಯಾಮರಾ ತೆರೆಯಿರಿ"</string>
<string name="recents_caption_resize" msgid="3517056471774958200">"ಹೊಸ ಕಾರ್ಯ ವಿನ್ಯಾಸವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
- <string name="cancel" msgid="6442560571259935130">"ರದ್ದುಮಾಡು"</string>
+ <string name="cancel" msgid="6442560571259935130">"ರದ್ದುಮಾಡಿ"</string>
<string name="accessibility_compatibility_zoom_button" msgid="8461115318742350699">"ಹೊಂದಾಣಿಕೆಯ ಝೂಮ್ ಬಟನ್."</string>
<string name="accessibility_compatibility_zoom_example" msgid="4220687294564945780">"ಚಿಕ್ಕ ಪರದೆಯಿಂದ ದೊಡ್ಡ ಪರದೆಗೆ ಝೂಮ್ ಮಾಡು."</string>
<string name="accessibility_bluetooth_connected" msgid="2707027633242983370">"ಬ್ಲೂಟೂತ್ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G ಡೇಟಾ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"ಸೆಲ್ಯುಲಾರ್ ಡೇಟಾ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ಡೇಟಾ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"ಏಕೆಂದರೆ ನಿಮ್ಮ ಹೊಂದಾಣಿಕೆ ಡೇಟಾ ಮೀತಿಯನ್ನು ತಲುಪಿದೆ, ಈ ಆವರ್ತನೆಯ ಉಳಿದ ಭಾಗಕ್ಕೆ ಸಾಧನವು ಡೇಟಾ ಬಳಕೆಯನ್ನು ವಿರಾಮಗೊಳಿಸಿದೆ.\n\nಮುಂದುವರೆಯುವಿಕೆಯು ನಿಮ್ಮ ವಾಹಕದ ಶುಲ್ಕಗಳಿಗೆ ಕಾರಣವಾಗಬಹುದು."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"ನೀವು ಹೊಂದಿಸಿರುವ ಡೇಟಾ ಮಿತಿ ತಲುಪಿದೆ. ನೀವು ಎಂದಿಗೂ ಸೆಲ್ಯುಲಾರ್ ಡೇಟಾವನ್ನು ಬಳಸದೆ ಇರಬಹುದು.\n\nನೀವು ಮುಂದುವರಿಸಿದರೆ, ಡೇಟಾ ಬಳಕೆಗೆ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ಮುಂದುವರಿಸು"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವಿಲ್ಲ"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ಎಚ್ಚರಿಕೆ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"ಕೆಲಸದ ಮೋಡ್"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ನೈಟ್ ಲೈಟ್"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"ನೈಟ್ ಲೈಟ್ ಆನ್ ಆಗಿದೆ, ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"ನೈಟ್ ಲೈಟ್ ಆಫ್ ಆಗಿದೆ, ಆನ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ನೀವು ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿರುವಿರಿ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 d477412..3827dba 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G 데이터 사용 중지됨"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"모바일 데이터 사용 중지됨"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"데이터 사용 중지됨"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"설정된 데이터 한도에 도달했기 때문에 기기에서 사이클의 나머지 기간 동안 데이터 사용을 일시 중지했습니다. \n\n데이터 사용을 재개하면 이동통신사 요금이 청구될 수 있습니다."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"설정한 데이터 한도에 도달했습니다. 더 이상 모바일 데이터를 사용하지 않습니다.\n\n계속 사용하면 데이터 사용 요금이 부과될 수 있습니다."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"재개"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"인터넷에 연결되지 않음"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi 연결됨"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> 경고"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"작업 모드"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"야간 조명"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"야간 조명 사용 설정됨, 사용 중지하려면 탭"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"야간 조명 사용 중지됨, 사용 설정하려면 탭"</string>
<string name="recents_empty_message" msgid="808480104164008572">"최근 항목이 없습니다."</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"모든 항목을 삭제했습니다."</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"애플리케이션 정보"</string>
@@ -466,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>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"메신저"</string>
+ <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 b84bea2..56c8360 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G дайындары тындырылды"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Уюлдук дайындар тындырылды"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Дайындар тындырылды"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Киргизиле турган дайындар белгиленген эң жогорку чекке жеткендиктен, ушул мерчимдин калган бөлүгүндө түзмөгүңүздө дайындардын колдонулушу тындырылды.\n\nУлантсаңыз, байланыш операторуңузга акы төлөп калышыңыз мүмкүн."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Трафик сиз койгон чекке жетти. Эми мобилдик дайындарды колдоно албайсыз.\n\nЭгер улантсаңыз, мобилдик дайындарды колдонгонуңуз үчүн акы алынышы мүмкүн."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Улантуу"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Интернет байланыш жок"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi байланышта"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> эскертүү"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Иштөө режими"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Түнкү жарык"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Түнкү жарык күйүк, өчүрүү үчүн таптап коюңуз"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Түнкү жарык өчүк, күйгүзүү үчүн таптап коюңуз"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Акыркы колдонмолор жок"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Баарын тазаладыңыз"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Колдонмо жөнүндө маалымат"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 f1be8b3..47a49da 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"ຂໍ້ມູນ 4G ຢຸດຊົ່ວຄາວແລ້ວ"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"ຂໍ້ມູນເຊວລູລາຢຸດຊົ່ວຄາວແລ້ວ"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ຂໍ້ມູນຢຸດຊົ່ວຄາວແລ້ວ"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"ເນື່ອງຈາກວ່າຮອດຂີດຈຳກັດຂໍ້ມູນທີ່ຕັ້ງໄວ້ຂອງທ່ານແລ້ວ, ອຸປະກອນຢຸດການນຳໃຊ້ຂໍ້ມູນສຳລັບສ່ວນທີ່ຍັງເຫຼືອຂອງຮອບວຽນນີ້.\n\nການເລີ່ມຕໍ່ອາດຈະນຳໄປສູ່ການປ່ຽນແປງຈາກຜູ້ໃຫ້ບໍລິການເຄືອຂ່າຍຂອງທ່ານ."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"ທ່ານໃຊ້ອິນເຕີເນັດຮອດຈຳນວນທີ່ທ່ານຈຳກັດປະລິມານໄວ້ແລ້ວ. ທ່ານຈະບໍ່ນຳໃຊ້ການເຊື່ອມຕໍ່ຜ່ານເຄືອຂ່າຍມືຖືອີກຕໍ່ໄປ.\n\nຫາກທ່ານສືບຕໍ່ໃຊ້ໄປອີກ, ທ່ານອາດຖືກຮຽກເກັບເງິນຄ່າບໍລິການໄດ້."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ເລີ່ມຕໍ່"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"ເຊື່ອມຕໍ່ Wi--Fi ແລ້ວ"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"ຄຳເຕືອນ <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"ໂໝດການເຮັດວຽກ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ແສງກາງຄືນ"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"ເປີດແສງກາງຄືນຢູ່, ແຕະເພື່ອປິດໄວ້"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"ປິດແສງກາງຄືນຢູ່, ແຕະເພື່ອເປີດໃຊ້"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ທ່ານລຶບລ້າງທຸກຢ່າງແລ້ວ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ຂໍ້ມູນແອັບພລິເຄຊັນ"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 8a0f956b..c4676fa 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G duomenys pristabdyti"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Korinio ryšio duomenys pristabdyti"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Duomenys pristabdyti"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Kadangi buvo pasiektas nustatytas duomenų limitas, įrenginys pristabdė duomenų naudojimą likusį šio ciklo laikotarpį.\n\nAtnaujinus gali būti taikomi operatoriaus mokesčiai."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Pasiektas nustatytas duomenų apribojimas. Nebenaudojate mobiliojo ryšio duomenų.\n\nJei atnaujinsite, gali būti taikomi mokesčiai už duomenų naudojimą."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Atnaujinti"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nėra interneto ryš."</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Prisij. prie „Wi-Fi“"</string>
@@ -323,8 +323,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> įspėjimas"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Darbo režimas"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nakties šviesa"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nakties šviesa įjungta. Palieskite, kad išjungtumėte"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nakties šviesa išjungta. Palieskite, kad įjungtumėte"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nėra jokių naujausių elementų"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Viską išvalėte"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Programos informacija"</string>
@@ -568,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"TP"</string>
+ <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 76d0da4..a8266dd 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -238,7 +238,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G datu lietojums ir apturēts"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilo datu lietojums ir apturēts"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Datu lietojums ir apturēts"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Tika sasniegts iestatītais datu lietojuma ierobežojums, tādēļ ierīcē ir apturēts datu lietojums cikla atlikušajā periodā.\n\nJa atsāksiet lietot datus, iespējams, jūsu mobilo sakaru operators iekasēs maksu."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Ir sasniegts jūsu iestatītais datu ierobežojums. Jūs vairs neizmantojat mobilos datus.\n\nJa atsāksiet, var tikt piemērota maksa par datu lietojumu."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Atsākt"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nav interneta sav."</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Izv. sav. ar Wi-Fi"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> brīdinājums"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Darba režīms"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nakts režīms"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nakts režīms ir ieslēgts. Pieskarieties, lai to izslēgtu."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nakts režīms ir izslēgts. Pieskarieties, lai to ieslēgtu."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nav nesenu vienumu"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Visi uzdevumi ir notīrīti"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informācija par lietojumprogrammu"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Tūlītējā ziņojumapmaiņa"</string>
+ <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 d7982c7..c5e2500e 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Податоците 4G се паузирани"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Мобилните податоци се паузирани"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Податоците се паузирани"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Поради тоа што го достигнавте поставеното ограничување на податоци, уредот го паузираше користењето податоци до крајот на циклусот.\n\nОператорот може да ви наплати ако продолжите."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Го достигнавте ограничувањето за сообраќај на податоци што сте го поставиле. Веќе не користите мобилен интернет.\n\nДоколку продолжите, ќе ви биде наплатено за потрошениот сообраќај."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Продолжи"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Нема интернет"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Поврзано на Wi-Fi"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Предупредување за <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Режим на работа"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ноќно светло"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Ноќното светло е вклучено, допрете за да се исклучи"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Ноќното светло е исклучено, допрете за да се вклучи"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Нема неодамнешни ставки"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Исчистивте сѐ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Информации за апликацијата"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 34aaa92..a1a3b31 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G ഡാറ്റ താൽക്കാലികമായി നിർത്തി"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"സെല്ലുലാർ ഡാറ്റ താൽക്കാലികമായി നിർത്തി"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ഡാറ്റ താൽക്കാലികമായി നിർത്തി"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"നിങ്ങൾ നേരത്തെ ക്രമീകരിച്ച ഡാറ്റ പരിധിയിലെത്തിയതിനാൽ, ഈ സൈക്കിളിന്റെ അവശേഷിക്കുന്ന ഡാറ്റ ഉപയോഗം, ഉപകരണം താൽക്കാലികമായി നിർത്തി.\n\nപുനരാരംഭിക്കുന്നത്, നിങ്ങളുടെ കാരിയറിൽ നിന്ന് നിരക്കുകൾക്ക് ഇടയാക്കാം."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"നിങ്ങൾ സജ്ജമാക്കിയ ഡാറ്റ പരിധി എത്തിക്കഴിഞ്ഞു. ഇനിയങ്ങോട്ട് നിങ്ങൾ സെല്ലുലാർ ഡാറ്റ ഉപയോഗിക്കില്ല.\n\nതുടരുകയാണെങ്കിൽ, ഡാറ്റാ ഉപയോഗത്തിന് നിരക്കുകൾ ബാധകമായേക്കാം."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"പുനരാരംഭിക്കുക"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ഇന്റർനെറ്റ് കണക്ഷൻ ഇല്ല"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"വൈഫൈ കണക്റ്റുചെയ്തു"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> മുന്നറിയിപ്പ്"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"പ്രവർത്തന മോഡ്"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"നൈറ്റ് ലൈറ്റ്"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"നൈറ്റ് ലൈറ്റ് ഓണാണ്, ഓഫാക്കുന്നതിന് ടാപ്പുചെയ്യുക"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"നൈറ്റ് ലൈറ്റ് ഓഫാണ്, ഓണാക്കുന്നതിന് ടാപ്പുചെയ്യുക"</string>
<string name="recents_empty_message" msgid="808480104164008572">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"നിങ്ങൾ എല്ലാം മായ്ച്ചിരിക്കുന്നു"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ആപ്പ് വിവരം"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 44f0125..9b83058 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -235,7 +235,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G дата-г түр зогсоосон байна"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Гар утасны дата-г түр зогсоосон байна"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Дата-г түр зогсоосон байна"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Таны багц дата эрхийн дээд хэмжээнд хүрсэн байгаа тул төхөөрөмж нь үлдсэн хэсэгт дата хэрэглээг түр зогсоосон байна.\n\nТа үйлдлийг үргэлжлүүлэхийг хүсвэл үйлчилгээ үзүүлж буй үүрэн холбооны газраас нэмж дата эрх авна уу."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Таны тогтоосон дата хэмжээний хязгаарт хүрсэн байна. Та одоогоор мобайл датаг ашиглаагүй байна.\n\nҮргэлжлүүлсэн тохиолдолд төлбөр гарах болно."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Үргэлжлүүлэх"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Интернет холболт байхгүй"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi холбогдсон"</string>
@@ -317,8 +317,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> анхааруулга"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Ажлын горим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Шөнийн гэрэл"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Шөнийн гэрэл асаалттай байна. Унтраахын тулд товшино уу"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Шөнийн гэрэл унтраалттай байна. Асаахын тулд товшино уу"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Сүүлийн үеийн зүйл байхгүй"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Та бүгдийг нь устгасан"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Аппликешны мэдээлэл"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 eeb16c7..8003890 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G डेटास विराम दिला आहे"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"सेल्युलर डेटास विराम दिला आहे"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"डेटास विराम दिला आहे"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"आपली सेट केलेली डेटा मर्यादा गाठल्यामुळे, डिव्हाइसने या चक्राच्या उर्वरित डेटा वापरास विराम दिला आहे.\n\nपुन्हा सुरु करण्यामुळे आपल्या वाहकाकडून शुल्क आकारले जाऊ शकते."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"आपण सेट केलेली डेटा मर्यादा गाठली आहे. आपण यापुढे मोबाइल डेटा वापरणार नाही.\n\nआपण पुन: सुरु केल्यास, डेटा वापरासाठी शुल्क आकारले जाऊ शकतात."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"पुन्हा सुरु करा"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"इंटरनेट कनेक्शन नाही"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"वाय-फाय कनेक्ट केले"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> चेतावणी"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"कार्य मोड"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"रात्रीचा प्रकाश"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"रात्रीचा प्रकाश चालू आहे, बंद करण्यासाठी टॅप करा"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"रात्रीचा प्रकाश बंद आहे, चालू करण्यासाठी टॅप करा"</string>
<string name="recents_empty_message" msgid="808480104164008572">"अलीकडील कोणतेही आयटम नाहीत"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"आपण सर्वकाही साफ केले"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"अनुप्रयोग माहिती"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 45215b4..1a16967 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Data 4G dijeda"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Data selular dijeda"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data dijeda"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Oleh kerana had data tetap anda telah dicapai, peranti telah menjeda penggunaan data bagi baki kitaran ini.\n\nMenyambung semula boleh menyebabkan anda dikenakan bayaran daripada pembawa anda."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Had data yang anda tetapkan telah dicapai. Anda tidak lagi menggunakan data selular.\n\nJika anda menyambung semula, caj mungkin dikenakan untuk penggunaan data."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Sambung semula"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Tiada smbg Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi disambungkan"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Amaran <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mod kerja"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Cahaya Malam"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Cahaya Malam dihidupkan, ketik untuk mematikannya"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Cahaya Malam dimatikan, ketik untuk menghidupkannya"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Tiada item terbaharu"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Anda telah mengetepikan semua item"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Maklumat Aplikasi"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 31efa64..17b6802 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G data ခေတ္တရပ်တန့်သည်"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"cellular data ခေတ္တရပ်တန့်သည်"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ဒေတာ ခေတ္တရပ်တန့်သည်"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"သင့် ဒေတာ အသုံးပြုမှု သတ်မှတ်ထားချက်သို့ ရောက်ရှိသောကြောင့်၊ ဤကာလအတွက် ကျန်ရှိသည့် ဒေတာအသုံးပြုမှုအား စက်ပစ္စည်းမှ ရပ်တန့်ထားသည်။\n\nဆက်လက်သွားပါက သင့်ဖုန်းဝန်ဆောင်မှုမှ သင့်အား ကုန်ကျစရိတ်တောင်းခံလိမ့်မည်။"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"သင်သတ်မှတ်ထားသော ဒေတာကန့်သတ်ချက်သို့ ရောက်နေပါပြီ။ သင်သည် ဆယ်လူလာဒေတာကို အသုံးမပြုတော့ပါ။\n\nသင်ဆက်လုပ်မည်ဆိုလျှင် ဒေတာသုံးစွဲမှုအတွက် အခငွေ ကျသင့်မှုရှိနိုင်ပါသည်။"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ပြန်ဆက်လုပ်ရန်"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"အင်တာနက်မရှိ"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"ကြိုးမဲ့ဆက်သွယ်မှု"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> သတိပေးချက်"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"အလုပ် မုဒ်"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ညအလင်းရောင်"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"ညအလင်းရောင်ကို ဖွင့်ထားသည်၊ ပိတ်ရန်တို့ပါ"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"ညအလင်းရောင်ကို ပိတ်ထားသည်၊ ဖွင့်ရန်တို့ပါ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"သင်အားလုံးကို ရှင်းလင်းပြီးပါပြီ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"အပလီကေးရှင်းအင်ဖို"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"အမြန်စာတိုစနစ်"</string>
+ <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>
@@ -587,7 +585,7 @@
<string name="center" msgid="4327473927066010960">"ဌာန"</string>
<string name="end" msgid="125797972524818282">"ပြီးပါပြီ"</string>
<string name="space" msgid="804232271282109749">"နေရာလွတ်ခြားစနစ်"</string>
- <string name="menu_ime" msgid="4943221416525250684">"မန်နယူး / ကီးဘုတ်ပြောင်းစနစ်"</string>
+ <string name="menu_ime" msgid="4943221416525250684">"မီနူး / ကီးဘုတ်ပြောင်းစနစ်"</string>
<string name="select_button" msgid="1597989540662710653">"ပေါင်းထည့်ရန် ခလုတ်ကိုရွေးပါ"</string>
<string name="add_button" msgid="4134946063432258161">"ခလုတ်ပေါင်းထည့်ပါ"</string>
<string name="save" msgid="2311877285724540644">"သိမ်းရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 9d13ebf..cb691c5 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-data er satt på pause"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobildata er satt på pause"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data er satt på pause"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Fordi den angitte datagrensen ble nådd, har enheten satt databruk på pause for resten av denne syklusen. \n\nHvis du gjenopptar bruken, kan det føre til avgifter fra operatøren din."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Datagrensen du satte, er nådd. Du bruker ikke mobildata lenger.\n\nHvis du gjenopptar bruk av mobildata, kan gebyrer for databruk påløpe."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Gjenoppta"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ingen Internett-forbindelse"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi tilkoblet"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advarsel for <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbeidsmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nattlys"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nattlys er på, trykk for å slå av"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nattlys er av, trykk for å slå på"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ingen nylige elementer"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du har fjernet alt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Appinformasjon"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Chat"</string>
+ <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 0a37810..97f5f5e 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G डेटा रोकिएको छ"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"सेल्यूलर डेटा रोकिएको छ"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"डेटा रोकिएको छ"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"तपाईंले सेट गर्नुभएको डेटाको सीमा पुगेकाले, यन्त्रले यस चक्रको बाँकी भागका लागि डेटाको प्रयोग रोकेको छ।\n\nपुन: सुरू गर्दा तपाईंको क्यारियरले शुल्कहरू लिन सक्छ।"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"तपाईँले सेट गर्नुभएको डेटाको सीमामा पुगिएको छ। अबदेखि तपाईँ सेलुलर डेटाको प्रयोग गर्नुहुने छैन। \n\nतपाईँले प्रयोग जारी राख्नुभयो भने डेटा प्रयोगका शुल्कहरू लाग्न सक्छन्।"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"पुनः सुरु गर्नुहोस्"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"इन्टरनेट जडान छैन"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi जडित"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> चेतावनी दिँदै"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"कार्य मोड"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"रात्रिको प्रकाश"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"रात्रिको प्रकाश सक्रिय छ, निष्क्रिय पार्न ट्याप गर्नुहोस्"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"रात्रिको प्रकाश निष्क्रिय छ, सक्रिय गर्न ट्याप गर्नुहोस्"</string>
<string name="recents_empty_message" msgid="808480104164008572">"हालका कुनै पनि वस्तुहरू छैनन्"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"तपाईँले सबै कुरा खाली गर्नुभएको छ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"अनुप्रयोग जानकारी"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 ef55b3c..43c577d 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-data zijn onderbroken"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobiele gegevens zijn onderbroken"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Gegevens zijn onderbroken"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Omdat de ingestelde gegevenslimiet is bereikt, heeft het apparaat het gegevensverbruik onderbroken voor de rest van deze cyclus.\n\nAls u het gegevensverbruik hervat, kan je provider kosten in rekening brengen."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"De ingestelde datalimiet is bereikt. Je gebruikt geen mobiele data meer.\n\nAls je hervat, kunnen er kosten voor datagebruik in rekening worden gebracht."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Hervatten"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Geen internetverbinding"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Verbonden via wifi"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Waarschuwing voor <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Werkmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nachtverlichting"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nachtverlichting is ingeschakeld. Tik om deze uit te schakelen."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nachtverlichting is uitgeschakeld. Tik om deze in te schakelen."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Geen recente items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Je hebt alles gewist"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"App-informatie"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Chat"</string>
+ <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 635e96e..6ef8674 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G ਡੈਟਾ ਰੁਕ ਗਿਆ ਹੈ"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"ਸੈਲਿਊਲਰ ਡੈਟਾ ਰੁਕ ਗਿਆ ਹੈ"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ਡੈਟਾ ਰੁਕ ਗਿਆ ਹੈ"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"ਕਿਉਂਕਿ ਤੁਹਾਡੀ ਸੈਟ ਡੈਟਾ ਸੀਮਾ ਪੂਰੀ ਹੋ ਗਈ ਸੀ, ਡੀਵਾਈਸ ਨੇ ਇਸ ਬਾਕੀ ਚੱਕਰ ਲਈ ਡੈਟਾ ਉਪਯੋਗ ਰੋਕ ਦਿੱਤਾ ਹੈ।\n\nਇਸਨੂੰ ਦੁਬਾਰਾ ਸ਼ੁਰੂ ਕਰਨ ਨਾਲ ਤੁਹਾਡੇ ਕੈਰੀਅਰ ਵੱਲੋਂ ਖ਼ਰਚੇ ਪਾਏ ਜਾ ਸਕਦੇ ਹਨ।"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"ਤੁਸੀਂ ਤੁਹਾਡੇ ਵੱਲੋਂ ਸੈੱਟ ਕੀਤੀ ਗਈ ਡੈਟਾ ਸੀਮਾ \'ਤੇ ਪਹੁੰਚ ਚੁੱਕੇ ਹੋ। ਤੁਸੀਂ ਹੁਣ ਸੈਲਿਊਲਰ ਡੈਟੇ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕਰ ਰਹੇ ਹੋ।\n\nਜੇਕਰ ਤੁਸੀਂ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਦੇ ਹੋ, ਤਾਂ ਡੈਟਾ ਵਰਤੋਂ ਲਈ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ।"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ਦੁਬਾਰਾ ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ਕੋਈ ਇੰਟਰਨੈਟ ਕਨੈਕਸ਼ਨ ਨਹੀਂ"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ਚਿਤਾਵਨੀ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"ਕੰਮ ਮੋਡ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ਰਾਤਰੀ ਲਾਈਟ"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"ਰਾਤਰੀ ਲਾਈਟ ਚਾਲੂ ਹੈ, ਬੰਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"ਰਾਤਰੀ ਲਾਈਟ ਬੰਦ ਹੈ, ਚਾਲੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮਾਂ ਨਹੀਂ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ਤੁਸੀਂ ਸਭ ਕੁਝ ਸਾਫ਼ ਕਰ ਦਿੱਤਾ ਹੈ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ਐਪਲੀਕੇਸ਼ਨ ਜਾਣਕਾਰੀ"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 5d3fd99..03e599c 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Transmisja danych 4G została wstrzymana"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Komórkowa transmisja danych została wstrzymana"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Transmisja danych została wstrzymana"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Ponieważ został osiągnięty ustawiony przez Ciebie limit danych, urządzenie wstrzymało użycie danych na pozostałą część tego cyklu.\n\nWznowienie może spowodować naliczenie opłat przez operatora."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Osiągnięto ustawiony limit danych. Nie korzystasz już z komórkowej transmisji danych.\n\nJeśli włączysz ją ponownie, może zostać naliczona opłata za transmisję danych."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Wznów"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Brak internetu"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: połączono"</string>
@@ -323,8 +323,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Ostrzeżenie: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Tryb pracy"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Podświetlenie nocne"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Podświetlenie nocne włączone – kliknij, by wyłączyć"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Podświetlenie nocne wyłączone – kliknij, by włączyć"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Brak ostatnich elementów"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Wszystko zostało wyczyszczone"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacje o aplikacji"</string>
@@ -568,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Komunikator"</string>
+ <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 298fc3c..0ae421d 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Os dados 4G foram pausados"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Os dados da rede celular foram pausados"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Os dados foram pausados"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Como seu limite de dados definido foi atingido, o dispositivo pausou o uso de dados para o restante deste ciclo.\n\nA retomada pode gerar cobranças por parte da sua operadora."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"O limite de dados que você definiu foi atingido. Você não está mais usando os dados da rede celular.\n\nSe retomar o uso de dados, cobranças poderão ser aplicadas."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Retomar"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sem conexão à Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabalho"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Modo noturno"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Modo noturno ativado. Toque para desativar"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Modo noturno desativado. Toque para ativar"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nenhum item recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Você limpou tudo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações do app"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Mensagens instantâneas"</string>
+ <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 33176b6..6ad19f9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Dados 4G em pausa"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Dados de redes móveis em pausa"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Dados em pausa"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Uma vez que foi atingido o limite de dados definido, foi interrompida a utilização de dados no seu dispositivo durante o tempo restante deste ciclo.\n\nSe retomar a utilização, o seu operador pode efetuar cobranças."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"O limite de dados definido foi atingido. Já não está a utilizar dados móveis.\n\nSe retomar, podem aplicar-se custos relativos à utilização de dados."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Retomar"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sem ligação internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ligado"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabalho"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz noturna"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Luz noturna ativada; toque para desativar"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Luz noturna desativada; toque para ativar"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nenhum item recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Limpou tudo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações da aplicação"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 298fc3c..0ae421d 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Os dados 4G foram pausados"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Os dados da rede celular foram pausados"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Os dados foram pausados"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Como seu limite de dados definido foi atingido, o dispositivo pausou o uso de dados para o restante deste ciclo.\n\nA retomada pode gerar cobranças por parte da sua operadora."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"O limite de dados que você definiu foi atingido. Você não está mais usando os dados da rede celular.\n\nSe retomar o uso de dados, cobranças poderão ser aplicadas."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Retomar"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sem conexão à Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabalho"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Modo noturno"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Modo noturno ativado. Toque para desativar"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Modo noturno desativado. Toque para ativar"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nenhum item recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Você limpou tudo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações do app"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Mensagens instantâneas"</string>
+ <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 9d6b7d9..70ef6ea 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -240,7 +240,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Conexiunea de date 4G este întreruptă"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Conexiunea de date mobile este întreruptă"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Conexiunea de date este întreruptă"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Deoarece limita setată pentru date a fost atinsă, dispozitivul a întrerupt utilizarea datelor pentru restul acestui ciclu.\n\nReluarea utilizării de date poate genera aplicarea de taxe de către operator."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"A fost atinsă limita de date setată. Datele mobile nu mai sunt folosite \n\nDacă reluați, este posibil să se aplice taxe pentru utilizarea datelor."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Reluați"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Fără conex. internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectat"</string>
@@ -323,12 +323,10 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avertizare: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modul de lucru"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Lumină de noapte"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Lumina de noapte este activată; atingeți pentru a o dezactiva"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Lumina de noapte este dezactivată; atingeți pentru a o activa"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Niciun element recent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Ați șters tot"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informații despre aplicație"</string>
- <string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixare pe ecran"</string>
+ <string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixarea ecranului"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"căutare"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> nu a putut porni."</string>
<string name="recents_launch_disabled_message" msgid="1624523193008871793">"Aplicația <xliff:g id="APP">%s</xliff:g> este dezactivată în modul sigur."</string>
@@ -568,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 9ac4155..41ec367 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Передача данных 4G приостановлена"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Передача мобильных данных приостановлена"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Передача данных приостановлена"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Передача данных выключена до конца цикла, поскольку достигнут установленный вами лимит.\n\nЕсли вы возобновите ее, оператор может взимать плату за дополнительный расход трафика."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Достигнут установленный вами лимит на передачу мобильных данных.\n\nЕсли вы продолжите, может взиматься дополнительная плата."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Возобновить"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Нет интернет-подключения"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi подключено"</string>
@@ -325,8 +325,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Предупреждение: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Рабочий режим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ночной режим"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Ночной режим включен. Нажмите, чтобы отключить."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Ночной режим отключен. Нажмите, чтобы включить."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Недавних приложений нет"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Вы очистили всё"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Сведения о приложении"</string>
@@ -570,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Чат"</string>
+ <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 95cac9e..8f65531 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G දත්ත විරාම කර ඇත"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"සෙලියුලර් දත්ත විරාම කර ඇත"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"දත්ත විරාම කර ඇත"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"ඔබ සකසා ඇති දත්ත සීමාවට ළඟා වූ නිසා, උපාංගය මගින් මෙම චක්රයේ ඉතිරිය සඳහා දත්ත භාවිතය විරාම කර ඇත. \n\nනැවත පටන් ගැනීමෙන් ඔබගේ වාහකයෙන් අය කිරීම් වලට පොළඹවනු ඇත."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"ඔබ සැකසූ දත්ත සීමාව ළඟා වී ඇත. ඔබ තවදුරටත් සෙලියුලර් දත්ත භාවිත නොකරයි. \n\nඔබ නැවත ආරම්භ කළහොත්, දත්ත භාවිතය සඳහා ගාස්තු අදාළ විය හැකිය."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"නැවත පටන්ගන්න"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"අන්තර්ජාල සම්බන්ධතාවයක් නැත"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi සම්බන්ධිතයි"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> අවවාද කිරීම"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"වැඩ ප්රකාරය"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"රාත්රී ආලෝකය"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"රාත්රී ආලෝකය ක්රියාත්මකයි, ක්රියාවිරහිත කිරීමට තට්ටු කරන්න"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"රාත්රී ආලෝකය ක්රියාවිරහිතයි, ක්රියාත්මක කිරීමට තට්ටු කරන්න"</string>
<string name="recents_empty_message" msgid="808480104164008572">"මෑත අයිතම නැත"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ඔබ සියලු දේ හිස් කර ඇත"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"යෙදුම් තොරතුරු"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 9c534c5..276c266 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Dátové prenosy 4G sú pozastavené"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobilné dáta sú pozastavené"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Dáta sú pozastavené"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Keďže ste dosiahli nastavený limit pre mobilné dáta, na zariadení bola pre zvyšok tohto cyklu pozastavená spotreba dát.\n\nJej opätovné spustenie môže mať za následok účtovanie poplatkov vaším operátorom."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Dosiahli ste nastavený limit dát. Už nepoužívate mobilné dátové pripojenie.\n\nAk ho však obnovíte, môžu vám byť účtované poplatky za spotrebu dát."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Znova spustiť"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Bez prip. na Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: pripojené"</string>
@@ -325,8 +325,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozornenie pri <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Pracovný režim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nočný režim"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nočný režim je zapnutý (vypnete ho klepnutím)"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nočný režim je vypnutý (zapnete ho klepnutím)"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Žiadne nedávne položky"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vymazali ste všetko"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informácie o aplikácii"</string>
@@ -570,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Okamžité správy"</string>
+ <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 d930a72..cda2ac9 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Prenos podatkov v omrežju 4G je zaustavljen"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Prenos mobilnih podatkov je zaustavljen"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Prenos podatkov je zaustavljen"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Dosegli ste nastavljeno omejitev količine prenesenih podatkov, zato je naprava zaustavila porabo podatkov za preostanek cikla.\n\nČe nadaljujete s porabo, boste morda morali plačati stroške operaterju."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Dosegli ste nastavljeno omejitev porabe podatkov. Prenosa podatkov v mobilnih omrežjih ne uporabljate več.\n\nČe nadaljujete, lahko nastanejo stroški prenosa podatkov."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Nadaljuj"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ni internetne povez."</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi povezan"</string>
@@ -325,8 +325,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Opozorilo – <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Način za delo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nočna svetloba"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nočna svetloba je vklopljena. Dotaknite se, če jo želite izklopiti."</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nočna svetloba je izklopljena. Dotaknite se, če jo želite vklopiti."</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ni nedavnih elementov"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vse te počistili"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Podatki o aplikaciji"</string>
@@ -570,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Neposredno sporočanje"</string>
+ <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 7c11eb9..33b2d5b 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Të dhënat 4G janë ndërprerë"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Të dhënat celulare janë ndërprerë"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Të dhënat janë ndërprerë"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Pajisja jote ka ndërprerë përdorimin e të dhënave për pjesën e mbetur të ciklit sepse është arritur kufiri i caktuar i të dhënave.\n\nVazhdimi mund të sjellë tarifa nga operatori celular."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Kufiri i të dhënave që ke caktuar është arritur. Nuk po përdor më të dhënat celulare.\n\nNëse vazhdon, mund të zbatohen tarifa për përdorimin e të dhënave."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Rifillo"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Nuk ka lidhje interneti"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi është i lidhur"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Paralajmërim për kufirin prej <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modaliteti i punës"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Drita e natës"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Drita e natës është joaktive, trokit për ta çaktivizuar"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Drita e natës është joaktive, trokit për ta aktivizuar"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nuk ka asnjë artikull të fundit"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"I ke pastruar të gjitha"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacioni i aplikacionit"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Mesazh i çastit"</string>
+ <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 30c1468..11d05b7 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -238,7 +238,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G подаци су паузирани"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Мобилни подаци су паузирани"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Подаци су паузирани"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Због тога што сте достигли подешено ограничење за податке, уређај је паузирао коришћење података током остатка овог циклуса.\n\nАко наставите, мобилни оператер може да вам наплати додатне трошкове."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Ограничење потрошње података које сте подесили је достигнуто. Више не користите мобилне податке.\n\nАко наставите, можда ће бити наплаћени трошкови за потрошњу података."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Настави"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Нема интернет везе"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi је повезан"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Упозорење за <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Режим рада"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ноћно светло"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Ноћно светло је укључено, додирните да бисте га искључили"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Ноћно светло је искључено, додирните да бисте га укључили"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Нема недавних ставки"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Обрисали сте све"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Информације о апликацији"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Размена тренутних порука"</string>
+ <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 b574e90b..46e3f05 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G-data har pausats"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobildata har pausats"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Dataanvändningen har pausats"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Eftersom du har nått den angivna datagränsen har dataanvändningen pausats under resten av perioden.\n\nOm du återupptar dataanvändningen kan avgifter från operatören tillkomma."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Den angivna datagränsen har uppnåtts. Du använder inte längre mobildata.\n\nOm du fortsätter kan avgifter för dataanvändning tillkomma."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Återuppta"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ingen anslutning"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi-ansluten"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Varning <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbetsläge"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nattljus"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Nattljus har aktiverats. Tryck för att inaktivera"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Nattljus har inaktiverats. Tryck för att aktivera"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Listan med de senaste åtgärderna är tom"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du har tömt listan"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Appinformation"</string>
@@ -330,7 +328,7 @@
<string name="recents_launch_disabled_message" msgid="1624523193008871793">"<xliff:g id="APP">%s</xliff:g> är inaktiverad i säkert läge."</string>
<string name="recents_stack_action_button_label" msgid="6593727103310426253">"Rensa alla"</string>
<string name="recents_incompatible_app_message" msgid="5075812958564082451">"Appen har inte stöd för delad skärm."</string>
- <string name="recents_drag_hint_message" msgid="2649739267073203985">"Dra här om du vill dela upp skärmen"</string>
+ <string name="recents_drag_hint_message" msgid="2649739267073203985">"Dra hit för att dela upp skärmen"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dela horisontellt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dela vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dela anpassad"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Snabbmeddelanden"</string>
+ <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 4e9d628..43aa36d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Data ya 4G imesitishwa"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Data ya simu ya mkononi imesitishwa"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Data imesitishwa"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Kwa sababu ulifikia kiwango cha juu cha data kilichowekwa, kifaa kimesitisha matumizi ya data katika awamu hii iliyosalia.\n\n Kuendelea kunaweza kusababisha malipo kwa mtoa huduma wako."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Umefikia kikomo cha data ulichoweka. Hutaweza kutumia tena data ya simu ya mkononi.\n\nIkiwa utaendelea, huenda ukatozwa ada za matumizi ya data."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Endelea"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Hakuna muunganisho wa mtandao"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Mtandao-hewa umeunganishwa"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Onyo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Hali ya kazi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Mwanga wa Usiku"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Umewasha hali ya Mwanga wa Usiku, gonga ili uizime"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Umezima hali ya Mwanga wa Usiku, gonga ili uiwashe"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Hakuna vipengee vya hivi karibuni"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Umeondoa vipengee vyote"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Maelezo ya Programu"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Ujumbe wa papo kwa papo"</string>
+ <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 a2ed16f..364173e 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G டேட்டா இடைநிறுத்தப்பட்டது"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"செல்லுலார் தரவு இடைநிறுத்தப்பட்டது"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"தரவு இடைநிறுத்தப்பட்டது"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"அமைக்கப்பட்ட தரவின் வரம்பை அடைந்துவிட்டதால், இந்தச் சுழற்சியின் மீதமுள்ள நாட்களுக்கான தரவுப் பயன்பாட்டைச் சாதனம் இடைநிறுத்தியுள்ளது.\n\nமீண்டும் தொடங்குவது, மொபைல் நிறுவனக் கட்டணங்களுக்கு உட்படுத்தலாம்."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"நீங்கள் அமைத்த தரவு வரம்பை அடைந்துவிட்டீர்கள். இப்போது செல்லுலார் தரவைப் பயன்படுத்த முடியாது.\n\nமீண்டும் தொடங்கினால், தரவுப் பயன்பாட்டிற்குக் கட்டணங்கள் விதிக்கப்படலாம்."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"மீண்டும் தொடங்கு"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"இணைய இணைப்பு இல்லை"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"வைஃபை இணைக்கப்பட்டது"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> எச்சரிக்கை"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"பணிப் பயன்முறை"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"இரவு ஒளி"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"இரவு ஒளி இயக்கப்பட்டுள்ளது. முடக்க, தட்டவும்"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"இரவு ஒளி முடக்கப்பட்டுள்ளது. இயக்க, தட்டவும்"</string>
<string name="recents_empty_message" msgid="808480104164008572">"சமீபத்திய பணிகள் இல்லை"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"எல்லாவற்றையும் அழித்துவிட்டீர்கள்"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"பயன்பாட்டு தகவல்"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 fdb5092..c1e55c8 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G డేటా పాజ్ చేయబడింది"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"సెల్యులార్ డేటా పాజ్ చేయబడింది"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"డేటా పాజ్ చేయబడింది"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"మీ సెట్ చేయబడిన డేటా పరిమితిని చేరుకున్నందున పరికరం ఈ సైకిల్లో మిగిలిన భాగానికి డేటా వినియోగాన్ని పాజ్ చేసింది.\n\nపునఃప్రారంభించడం వలన మీ క్యారియర్ ఛార్జీలు విధించవచ్చు."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"మీరు సెట్ చేసిన డేటా పరిమితిని చేరుకున్నారు. మీరు ఇప్పుడు సెల్యులార్ డేటాను ఉపయోగించడం లేదు.\n\nమీరు పునఃప్రారంభిస్తే, డేటా వినియోగానికి ఛార్జీలు వర్తించవచ్చు."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"పునఃప్రారంభించు"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ఇంటర్నెట్ కనెక్షన్ లేదు"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi కనెక్ట్ చేయబడింది"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> హెచ్చరిక"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"పని మోడ్"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"రాత్రి కాంతి"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"రాత్రి కాంతి ఆన్లో ఉంది, ఆఫ్ చేయడానికి నొక్కండి"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"రాత్రి కాంతి ఆఫ్లో ఉంది, ఆన్ చేయడానికి నొక్కండి"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ఇటీవలి అంశాలు ఏవీ లేవు"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"మీరు అన్నింటినీ తీసివేసారు"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"అనువర్తన సమాచారం"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 678937a..f2b1772 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"หยุดการใช้ข้อมูล 4G ชั่วคราวแล้ว"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"หยุดการใช้ข้อมูลมือถือชั่วคราวแล้ว"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"หยุดการใช้ข้อมูลชั่วคราวแล้ว"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"เนื่องจากใช้งานข้อมูลถึงขีดจำกัดที่กำหนดไว้แล้ว อุปกรณ์จึงหยุดการใช้งานข้อมูลไว้ชั่วคราวตลอดระยะเวลาที่เหลือของรอบนี้\n\nการทำให้กลับมาทำงานอีกครั้งอาจทำให้เกิดค่าใช้จ่ายจากผู้ให้บริการ"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"คุณใช้อินเทอร์เน็ตเกินปริมาณที่กำหนดไว้ ระบบจะไม่ใช้เครือข่ายมือถือต่อไป\n\nหากใช้ต่อ อาจมีค่าบริการตามปริมาณการใช้อินเทอร์เน็ต"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ทำต่อ"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ไม่มีอินเทอร์เน็ต"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"เชื่อมต่อ WiFi แล้ว"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"คำเตือน <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"โหมดการทำงาน"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"แสงตอนกลางคืน"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"แสงตอนกลางคืนเปิดอยู่ แตะเพื่อปิด"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"แสงตอนกลางคืนปิดอยู่ แตะเพื่อเปิด"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ไม่มีรายการล่าสุด"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"คุณได้ล้างทุกอย่างแล้ว"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ข้อมูลแอปพลิเคชัน"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 50c7e5c..f58ed17 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Naka-pause ang 4G data"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Naka-pause ang cellular data"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Naka-pause ang data"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Dahil naabot ang iyong nakatakdang limitasyon sa data, na-pause ng device ang paggamit ng data para sa nalalabing bahagi ng cycle na ito.\n\nMaaaring makakuha ng mga singilin mula sa iyong carrier ang pagpapatuloy."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Naabot na ang limitasyon sa data na itinakda mo. Hindi ka na gumagamit ng cellular data.\n\nKung magpapatuloy ka, maaari kang masingil para sa paggamit ng data."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Ipagpatuloy"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Walang koneksyon sa Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"nakakonekta ang Wi-Fi"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Babala sa <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Naka-on ang Night Light, i-tap upang i-off"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Naka-off ang Night Light, i-tap upang i-on"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Walang mga kamakailang item"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Na-clear mo ang lahat"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Impormasyon ng Application"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 575d011..e7b1abb 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G veri kullanımı duraklatıldı"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Hücresel veri kullanımı duraklatıldı"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Veri kullanımı duraklatıldı"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Ayarlanmış olan veri sınırınıza ulaşıldığından, bu dönemin kalan süresi için cihazda veri kullanımı duraklatıldı.\n\nVeri kullanımını devam ettirmek, operatörünüzün sizden ödeme almasına neden olabilir."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Ayarladığınız veri limitine ulaşıldı. Artık hücresel verilerinizi kullanmıyorsunuz.\n\nHücresel veri kullanımını devam ettirirseniz veri kullanım ücretleri ödemeniz gerekebilir."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Devam ettir"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"İnternet bağlantısı yok"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Kablosuz bağlandı"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> uyarısı"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Çalışma modu"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Gece Işığı"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Geçe Işığı açık, kapatmak için dokunun"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Gece Işığı kapalı, açmak için dokunun"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Yeni öğe yok"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Her şeyi sildiniz"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Uygulama Bilgileri"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 4d4162c..886a138 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -241,7 +241,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Передавання даних 4G призупинено"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Передавання мобільних даних призупинено"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Передавання даних призупинено"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Пристрій призупинив передавання даних до кінця цього циклу, оскільки ваш ліміт перевищено.\n\nЯкщо ви відновите передавання даних, оператор може стягувати додаткову плату."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Ви досягнули вказаного ліміту даних. Мобільний трафік вимкнено.\n\nЯкщо продовжите, може стягуватися плата за використання трафіку."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Відновити"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Немає з’єднання"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi під’єднано"</string>
@@ -325,8 +325,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Застереження: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Робочий режим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Нічний режим"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Нічний режим увімкнено. Торкніться, щоб вимкнути його"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Нічний режим вимкнено. Торкніться, щоб увімкнути його"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Немає нещодавніх завдань"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Ви очистили все"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Інформація про додаток"</string>
@@ -570,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Чат"</string>
+ <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 6a3e4db..31d90e2 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G ڈیٹا موقوف کر دیا گیا"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"سیلولر ڈیٹا موقوف کر دیا گیا"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"ڈیٹا موقوف کر دیا گیا"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"چونکہ آپ کی سیٹ کردہ ڈیٹا کی حد تک پہنچ گیا، لہذا آلہ نے اس سائیکل کے بقیہ حصے کیلئے ڈیٹا کے استعمال کو موقوف کر دیا ہے۔\n\nدوبارہ شروع کرنے سے آپ کے کیریئر سے چارجز لگ سکتے ہیں۔"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"آپ کی سیٹ کردہ ڈیٹا کی حد پوری ہو گئی ہے۔ آپ اب سیلولر ڈیٹا استعمال نہیں کر رہے۔\n\nاگر آپ دوبارہ شروع کرتے ہیں تو ڈیٹا کے استعمال کے چارجز لاگو ہو سکتے ہیں۔"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"دوبارہ شروع کریں"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"کوئی انٹرنیٹ کنکشن نہیں"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi مربوط ہے"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> وارننگ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"کام موڈ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"نائٹ لائٹ"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"نائٹ لائٹ آن ہے، آف کرنے کیلئے تھپتھپائیں"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"نائٹ لائٹ آف ہے، آن کرنے کیلئے تھپتھپائیں"</string>
<string name="recents_empty_message" msgid="808480104164008572">"کوئی حالیہ آئٹم نہیں"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"آپ نے سب کچھ صاف کر دیا ہے"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ایپلیکیشن کی معلومات"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 f7bae9b..78ad148 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G internet to‘xtatib qo‘yildi"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Mobil internetdan foydalanish to‘xtatib qo‘yildi"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Internetdan foydalanish to‘xtatib qo‘yildi"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Siz o‘rnatgan mobil internet chekloviga yetgani bois joriy hisob-kitob davrining qolgan muddati uchun mobil internetdan foydalanish vaqtinchalik to‘xtatib qo‘yildi.\n\nAgar internetdan foydalanishni davom ettirsangiz, buning uchun uyali aloqa operatoringiz ortiqcha haq talab qilishi mumkin."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"O‘rnatilgan trafik sarflab bo‘lindi. Endi mobil internetdan foydalana olmaysiz.\n\nDavom ettiradigan bo‘lsangiz, trafik uchun to‘lov olinishi mumkin."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Davom etish"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Internetga ulanmagan"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ulandi"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Ogohlantirish: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Ish rejimi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Tungi rejim"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Tunji rejim yoniq, o‘chirish uchun bosing"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Tunji rejim o‘chiq, yoqish uchun bosing"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Hozircha hech narsa yo‘q"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Hammasi o‘chirildi"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Ilova haqida ma’lumot"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"IM"</string>
+ <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 df6fdb9..ba49055 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Đã tạm dừng dữ liệu 4G"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Đã tạm dừng dữ liệu di động"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Đã tạm dừng dữ liệu"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Vì bạn đã đạt tới giới hạn dữ liệu thiết lập nên thiết bị đã tạm dừng sử dụng dữ liệu cho phần còn lại của chu kỳ này.\n\nTiếp tục có thể dẫn tới nhà cung cấp dịch vụ của bạn sẽ tính phí."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Đã đạt đến giới hạn dữ liệu mà bạn đặt. Bạn hiện không còn sử dụng dữ liệu di động.\n\nNếu tiếp tục, bạn có thể bị tính phí khi sử dụng dữ liệu."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Tiếp tục"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Ko có k.nối Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Đã kết nối Wi-Fi"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Cảnh báo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Chế độ làm việc"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Đèn đọc sách"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Đèn đọc sách được bật, nhấn để tắt"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Đèn đọc sách bị tắt, nhấn để bật"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Không có mục gần đây nào"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Bạn đã xóa mọi nội dung"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Thông tin ứng dụng"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"Nhắn tin nhanh"</string>
+ <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 7bda7a0..7a7991f 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G 数据网络已暂停使用"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"移动数据网络已暂停使用"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"数据网络已暂停使用"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"由于使用的数据流量已达到您所设置的上限,因此您的设备已暂停在此周期的剩余时间内使用数据流量。\n\n如果恢复数据流量使用,您的运营商可能会向您收取相应费用。"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"您的数据用量已达到设置的上限。您无法再使用移动数据网络。\n\n如果您继续操作,可能需要支付相应的数据流量费用。"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"恢复"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"未连接互联网"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"已连接到WLAN网络"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g>警告"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"工作模式"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"夜间模式"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"夜间模式已开启,点按即可关闭"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"夜间模式已关闭,点按即可开启"</string>
<string name="recents_empty_message" msgid="808480104164008572">"近期没有任何内容"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"您已清除所有内容"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"应用信息"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"即时通讯"</string>
+ <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 9dbf41f..4f7ffe5 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -239,7 +239,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"已暫停 4G 數據"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"已暫停流動數據"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"已暫停使用數據"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"由於您已達到設定的數據用量上限,裝置已暫停使用數據,直到週期結束。\n\n如恢復使用數據,流動網絡供應商可能會向您收取費用。"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"已達到您設定的數據上限。系統將停止使用流動數據網絡。\n\n如果您恢復使用流動數據網絡,可能需要支付數據費用。"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"恢復"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"沒有互聯網連線"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi 已連線"</string>
@@ -321,8 +321,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> 警告"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"工作模式"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"夜燈模式"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"夜燈模式已開啟,輕按即可關閉"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"夜燈模式已關閉,輕按即可開啟"</string>
<string name="recents_empty_message" msgid="808480104164008572">"沒有最近項目"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"您已清除所有項目"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"應用程式資料"</string>
@@ -566,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"即時通訊"</string>
+ <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 b061037..2de07e3 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"已暫停 4G 數據連線"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"已暫停行動數據連線"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"已暫停數據連線"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"由於數據用量已達設定上限,裝置在這個週期的剩餘時間將暫停使用數據連線。\n\n如果恢復使用,行動通訊業者可能會向您收取額外的連線費用。"</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"你的數據用量已達設定的用量上限,因此系統已停止使用行動數據連線。\n\n如果你繼續使用行動數據連線,可能需要支付相關的數據傳輸費用。"</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"恢復連線"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"沒有網際網路連線"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi 已連線"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> 警告"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"工作模式"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"夜燈"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"夜燈功能已開啟,輕觸即可關閉"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"夜燈功能已關閉,輕觸即可開啟"</string>
<string name="recents_empty_message" msgid="808480104164008572">"最近沒有任何項目"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"您已清除所有工作"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"應用程式資訊"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"即時訊息"</string>
+ <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 f97e71a2..6be736a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -237,7 +237,7 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"4G idatha imisiwe"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="4651001290947318931">"Idatha yeselula imisiwe"</string>
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Idatha imisiwe"</string>
- <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"Ngoba umkhawulo wakho wedatha osethiwe ufinyelelwe, idivayisi imise kancane ukusetshenziswa kwedatha ngesikhumbuzi salo mjikelezo.\n\nUkuqhuba futhi kungaholela kuzindleko kusuka kwinkampani yakho yenethiwekhi."</string>
+ <string name="data_usage_disabled_dialog" msgid="1841738975235283398">"Umkhawulo wedatha owusethayo ufikiwe. Awusasebenzisi idatha yeselula.\n\nUma uqalisa kabusha, izindleko zingasebenza ekusetshenzisweni kwedatha."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Qalisa kabusha"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Alukho uxhumano lwe-Inthanethi"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"I-Wi-Fi ixhunyiwe"</string>
@@ -319,8 +319,6 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> isexwayiso"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Imodi yomsebenzi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ukukhanya kwasebusuku"</string>
- <string name="quick_settings_night_display_summary_on" msgid="1814901757887526769">"Ukukhanya kwasebusuku kuvuliwe, thepha ukuze uvale"</string>
- <string name="quick_settings_night_display_summary_off" msgid="7892102914128777905">"Ukukhanya kwasebusuku kuvaliwe, thepha ukuze uvule"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Azikho izinto zakamuva"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Usule yonke into"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Ulwazi lohlelo lokusebenza"</string>
@@ -564,7 +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>
- <string name="keyboard_shortcut_group_applications_im" msgid="1892749399083161405">"I-IM"</string>
+ <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/config.xml b/packages/SystemUI/res/values/config.xml
index eb1a1eb..9eea375 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -209,9 +209,6 @@
<!-- Doze: should the significant motion sensor be used as a pulse signal? -->
<bool name="doze_pulse_on_significant_motion">false</bool>
- <!-- Doze: should the pickup sensor be used as a pulse signal? -->
- <bool name="doze_pulse_on_pick_up">false</bool>
-
<!-- Doze: check proximity sensor before pulsing? -->
<bool name="doze_proximity_check_before_pulse">true</bool>
@@ -240,9 +237,6 @@
-->
<string name="doze_pickup_subtype_performs_proximity_check"></string>
- <!-- Type of the double tap sensor. Empty if double tap is not supported. -->
- <string name="doze_double_tap_sensor_type" translatable="false"></string>
-
<!-- Doze: pulse parameter - how long does it take to fade in? -->
<integer name="doze_pulse_duration_in">900</integer>
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/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml
index 04402b7..988caf5 100644
--- a/packages/SystemUI/res/values/dimens_car.xml
+++ b/packages/SystemUI/res/values/dimens_car.xml
@@ -29,4 +29,5 @@
<dimen name="car_fullscreen_user_pod_image_avatar_height">128dp</dimen>
<dimen name="car_fullscreen_user_pod_text_size">24sp</dimen>
<dimen name="car_navigation_button_width">64dp</dimen>
+ <dimen name="car_navigation_bar_width">760dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3f485c3..1b7a922 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -755,10 +755,6 @@
<string name="quick_settings_work_mode_label">Work mode</string>
<!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
<string name="quick_settings_night_display_label">Night Light</string>
- <!-- QuickSettings: Summary for the toggle to deactivate Night display when it's on (renamed "Night Light" with title caps). [CHAR LIMIT=NONE] -->
- <string name="quick_settings_night_display_summary_on">Night Light on, tap to turn off</string>
- <!-- QuickSettings: Label for the toggle to activate Night display when it's off (renamed "Night Light" with title caps). [CHAR LIMIT=NONE] -->
- <string name="quick_settings_night_display_summary_off">Night Light off, tap to turn on</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent items</string>
@@ -1438,8 +1434,8 @@
<string name="keyboard_shortcut_group_applications_contacts">Contacts</string>
<!-- User visible title for the keyboard shortcut that takes the user to the email app. -->
<string name="keyboard_shortcut_group_applications_email">Email</string>
- <!-- User visible title for the keyboard shortcut that takes the user to the instant messaging app. -->
- <string name="keyboard_shortcut_group_applications_im">IM</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. -->
+ <string name="keyboard_shortcut_group_applications_sms">SMS</string>
<!-- User visible title for the keyboard shortcut that takes the user to the music app. -->
<string name="keyboard_shortcut_group_applications_music">Music</string>
<!-- User visible title for the keyboard shortcut that takes the user to the YouTube app. -->
@@ -1671,4 +1667,24 @@
not appear on production builds ever. -->
<string name="plugins" translatable="false">Plugins</string>
+ <!-- PIP section of the tuner. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
+
+ <!-- PIP swipe to dismiss title. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_swipe_to_dismiss_title" translatable="false">Swipe to dismiss</string>
+
+ <!-- PIP swipe to dismiss description. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_swipe_to_dismiss_summary" translatable="false">Swipe left or right off screen to close the PIP</string>
+
+ <!-- PIP drag to dismiss title. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_drag_to_dismiss_title" translatable="false">Drag to dismiss</string>
+
+ <!-- PIP drag to dismiss description. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string>
+
</resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 211f8e8..46a9ee3 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -121,6 +121,24 @@
</PreferenceScreen>
+ <PreferenceScreen
+ android:key="picture_in_picture"
+ android:title="@string/picture_in_picture">
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="pip_swipe_to_dismiss"
+ android:title="@string/pip_swipe_to_dismiss_title"
+ android:summary="@string/pip_swipe_to_dismiss_summary"
+ sysui:defValue="true" />
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="pip_drag_to_dismiss"
+ android:title="@string/pip_drag_to_dismiss_title"
+ android:summary="@string/pip_drag_to_dismiss_summary"
+ sysui:defValue="true" />
+
+ </PreferenceScreen>
+
<!--
<Preference
android:key="nav_bar"
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index 076b5bc..e1d6a94 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -31,10 +31,9 @@
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.provider.Settings;
-
import com.android.systemui.statusbar.policy.BatteryController;
public class BatteryMeterDrawable extends Drawable implements
@@ -92,18 +91,17 @@
private int mLightModeBackgroundColor;
private int mLightModeFillColor;
- private final SettingObserver mSettingObserver = new SettingObserver();
+ private final SettingObserver mSettingObserver;
private final Context mContext;
- private final Handler mHandler;
private int mLevel = -1;
private boolean mPluggedIn;
private boolean mListening;
- public BatteryMeterDrawable(Context context, Handler handler, int frameColor) {
+ public BatteryMeterDrawable(Context context, int frameColor) {
mContext = context;
- mHandler = handler;
+ mSettingObserver = new SettingObserver(new Handler(mContext.getMainLooper()));
final Resources res = context.getResources();
TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
@@ -199,12 +197,7 @@
}
private void postInvalidate() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- invalidateSelf();
- }
- });
+ scheduleSelf(this::invalidateSelf, 0);
}
public void setBatteryController(BatteryController batteryController) {
@@ -506,8 +499,8 @@
}
private final class SettingObserver extends ContentObserver {
- public SettingObserver() {
- super(new Handler());
+ public SettingObserver(Handler handler) {
+ super(handler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index d8b95cc..4f3ffde 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -49,7 +49,7 @@
defStyle, 0);
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.batterymeter_frame_color));
- mDrawable = new BatteryMeterDrawable(context, new Handler(), frameColor);
+ mDrawable = new BatteryMeterDrawable(context, frameColor);
atts.recycle();
mSlotBattery = context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/BootReceiver.java b/packages/SystemUI/src/com/android/systemui/BootReceiver.java
deleted file mode 100644
index 8e24eeb..0000000
--- a/packages/SystemUI/src/com/android/systemui/BootReceiver.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.util.Log;
-
-/**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
- */
-public class BootReceiver extends BroadcastReceiver {
- private static final String TAG = "SystemUIBootReceiver";
-
- @Override
- public void onReceive(final Context context, Intent intent) {
- try {
- // Start the load average overlay, if activated
- ContentResolver res = context.getContentResolver();
- if (Settings.Global.getInt(res, Settings.Global.SHOW_PROCESSES, 0) != 0) {
- Intent loadavg = new Intent(context, com.android.systemui.LoadAverageService.class);
- context.startService(loadavg);
- }
- } catch (Exception e) {
- Log.e(TAG, "Can't start load average service", e);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index f76a68c..a4d78c5 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -173,11 +173,7 @@
if (DEBUG) {
Log.d(TAG, "trimMemory");
}
- mBackground.recycle();
- mBackground = null;
- mBackgroundWidth = -1;
- mBackgroundHeight = -1;
- mWallpaperManager.forgetLoadedWallpaper();
+ unloadWallpaper(true /* forgetSize */);
}
}
@@ -199,7 +195,7 @@
public void onDestroy() {
super.onDestroy();
mBackground = null;
- mWallpaperManager.forgetLoadedWallpaper();
+ unloadWallpaper(true /* forgetSize */);
}
boolean updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo,
@@ -209,8 +205,7 @@
// Load background image dimensions, if we haven't saved them yet
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Need to load the image to get dimensions
- mWallpaperManager.forgetLoadedWallpaper();
- loadWallpaper(forDraw);
+ loadWallpaper(forDraw, true /* needsReset */);
if (DEBUG) {
Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
}
@@ -366,8 +361,7 @@
((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
dw + ", " + dh);
}
- mWallpaperManager.forgetLoadedWallpaper();
- loadWallpaper(true /* needDraw */);
+ loadWallpaper(true /* needDraw */, true /* needReset */);
if (DEBUG) {
Log.d(TAG, "Reloading, resuming draw later");
}
@@ -426,8 +420,7 @@
// position it appropriately. As such, we no longer needed
// the loaded bitmap. Yay!
// hw-accelerated renderer retains bitmap for faster rotation
- mBackground = null;
- mWallpaperManager.forgetLoadedWallpaper();
+ unloadWallpaper(false /* forgetSize */);
}
}
}
@@ -438,25 +431,39 @@
*
* If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
* the active request).
+ *
+ * If {@param needsReset} is set also clears the cache in WallpaperManager first.
*/
- private void loadWallpaper(boolean needsDraw) {
+ private void loadWallpaper(boolean needsDraw, boolean needsReset) {
mNeedsDrawAfterLoadingWallpaper |= needsDraw;
if (mLoader != null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping loadWallpaper, already in flight ");
+ if (needsReset) {
+ mLoader.cancel(false /* interrupt */);
+ mLoader = null;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping loadWallpaper, already in flight ");
+ }
+ return;
}
- return;
}
mLoader = new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Throwable exception;
try {
+ if (needsReset) {
+ mWallpaperManager.forgetLoadedWallpaper();
+ }
return mWallpaperManager.getBitmap();
} catch (RuntimeException | OutOfMemoryError e) {
exception = e;
}
+ if (isCancelled()) {
+ return null;
+ }
+
if (exception != null) {
// Note that if we do fail at this, and the default wallpaper can't
// be loaded, we will go into a cycle. Don't do a build where the
@@ -469,6 +476,10 @@
Log.w(TAG, "Unable reset to default wallpaper!", ex);
}
+ if (isCancelled()) {
+ return null;
+ }
+
try {
return mWallpaperManager.getBitmap();
} catch (RuntimeException | OutOfMemoryError e) {
@@ -505,6 +516,26 @@
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ private void unloadWallpaper(boolean forgetSize) {
+ if (mLoader != null) {
+ mLoader.cancel(false);
+ mLoader = null;
+ }
+ mBackground = null;
+ if (forgetSize) {
+ mBackgroundWidth = -1;
+ mBackgroundHeight = -1;
+ }
+
+ mLoader = new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ mWallpaperManager.forgetLoadedWallpaper();
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
@Override
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
super.dump(prefix, fd, out, args);
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
new file mode 100644
index 0000000..c14b17f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.SystemClock;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.LatencyTracker;
+import com.android.systemui.statusbar.phone.FingerprintUnlockController;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+/**
+ * Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
+ * system that are used for testing the latency.
+ */
+public class LatencyTester extends SystemUI {
+
+ private static final String ACTION_FINGERPRINT_WAKE =
+ "com.android.systemui.latency.ACTION_FINGERPRINT_WAKE";
+ private static final String ACTION_TURN_ON_SCREEN =
+ "com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+
+ @Override
+ public void start() {
+ if (!Build.IS_DEBUGGABLE) {
+ return;
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_FINGERPRINT_WAKE);
+ filter.addAction(ACTION_TURN_ON_SCREEN);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_FINGERPRINT_WAKE.equals(action)) {
+ fakeWakeAndUnlock();
+ } else if (ACTION_TURN_ON_SCREEN.equals(action)) {
+ fakeTurnOnScreen();
+ }
+ }
+ }, filter);
+ }
+
+ private void fakeTurnOnScreen() {
+ PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(
+ LatencyTracker.ACTION_TURN_ON_SCREEN);
+ }
+ powerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:LATENCY_TESTS");
+ }
+
+ private void fakeWakeAndUnlock() {
+ FingerprintUnlockController fingerprintUnlockController = getComponent(PhoneStatusBar.class)
+ .getFingerprintUnlockController();
+ fingerprintUnlockController.onFingerprintAcquired();
+ fingerprintUnlockController.onFingerprintAuthenticated(
+ KeyguardUpdateMonitor.getCurrentUser());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/LoadAverageService.java b/packages/SystemUI/src/com/android/systemui/LoadAverageService.java
deleted file mode 100644
index 59ffe03..0000000
--- a/packages/SystemUI/src/com/android/systemui/LoadAverageService.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * 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.systemui;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.view.Gravity;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.internal.os.ProcessCpuTracker;
-
-public class LoadAverageService extends Service {
- private View mView;
-
- private static final class CpuTracker extends ProcessCpuTracker {
- String mLoadText;
- int mLoadWidth;
-
- private final Paint mPaint;
-
- CpuTracker(Paint paint) {
- super(false);
- mPaint = paint;
- }
-
- @Override
- public void onLoadChanged(float load1, float load5, float load15) {
- mLoadText = load1 + " / " + load5 + " / " + load15;
- mLoadWidth = (int)mPaint.measureText(mLoadText);
- }
-
- @Override
- public int onMeasureProcessName(String name) {
- return (int)mPaint.measureText(name);
- }
- }
-
- private class LoadView extends View {
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == 1) {
- mStats.update();
- updateDisplay();
- Message m = obtainMessage(1);
- sendMessageDelayed(m, 2000);
- }
- }
- };
-
- private final CpuTracker mStats;
-
- private Paint mLoadPaint;
- private Paint mAddedPaint;
- private Paint mRemovedPaint;
- private Paint mShadowPaint;
- private Paint mShadow2Paint;
- private Paint mIrqPaint;
- private Paint mSystemPaint;
- private Paint mUserPaint;
- private float mAscent;
- private int mFH;
-
- private int mNeededWidth;
- private int mNeededHeight;
-
- LoadView(Context c) {
- super(c);
-
- setPadding(4, 4, 4, 4);
- //setBackgroundResource(com.android.internal.R.drawable.load_average_background);
-
- // Need to scale text size by density... but we won't do it
- // linearly, because with higher dps it is nice to squeeze the
- // text a bit to fit more of it. And with lower dps, trying to
- // go much smaller will result in unreadable text.
- int textSize = 10;
- float density = c.getResources().getDisplayMetrics().density;
- if (density < 1) {
- textSize = 9;
- } else {
- textSize = (int)(10*density);
- if (textSize < 10) {
- textSize = 10;
- }
- }
- mLoadPaint = new Paint();
- mLoadPaint.setAntiAlias(true);
- mLoadPaint.setTextSize(textSize);
- mLoadPaint.setARGB(255, 255, 255, 255);
-
- mAddedPaint = new Paint();
- mAddedPaint.setAntiAlias(true);
- mAddedPaint.setTextSize(textSize);
- mAddedPaint.setARGB(255, 128, 255, 128);
-
- mRemovedPaint = new Paint();
- mRemovedPaint.setAntiAlias(true);
- mRemovedPaint.setStrikeThruText(true);
- mRemovedPaint.setTextSize(textSize);
- mRemovedPaint.setARGB(255, 255, 128, 128);
-
- mShadowPaint = new Paint();
- mShadowPaint.setAntiAlias(true);
- mShadowPaint.setTextSize(textSize);
- //mShadowPaint.setFakeBoldText(true);
- mShadowPaint.setARGB(192, 0, 0, 0);
- mLoadPaint.setShadowLayer(4, 0, 0, 0xff000000);
-
- mShadow2Paint = new Paint();
- mShadow2Paint.setAntiAlias(true);
- mShadow2Paint.setTextSize(textSize);
- //mShadow2Paint.setFakeBoldText(true);
- mShadow2Paint.setARGB(192, 0, 0, 0);
- mLoadPaint.setShadowLayer(2, 0, 0, 0xff000000);
-
- mIrqPaint = new Paint();
- mIrqPaint.setARGB(0x80, 0, 0, 0xff);
- mIrqPaint.setShadowLayer(2, 0, 0, 0xff000000);
- mSystemPaint = new Paint();
- mSystemPaint.setARGB(0x80, 0xff, 0, 0);
- mSystemPaint.setShadowLayer(2, 0, 0, 0xff000000);
- mUserPaint = new Paint();
- mUserPaint.setARGB(0x80, 0, 0xff, 0);
- mSystemPaint.setShadowLayer(2, 0, 0, 0xff000000);
-
- mAscent = mLoadPaint.ascent();
- float descent = mLoadPaint.descent();
- mFH = (int)(descent - mAscent + .5f);
-
- mStats = new CpuTracker(mLoadPaint);
- mStats.init();
- updateDisplay();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mHandler.sendEmptyMessage(1);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mHandler.removeMessages(1);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(resolveSize(mNeededWidth, widthMeasureSpec),
- resolveSize(mNeededHeight, heightMeasureSpec));
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- final int W = mNeededWidth;
- final int RIGHT = getWidth()-1;
-
- final CpuTracker stats = mStats;
- final int userTime = stats.getLastUserTime();
- final int systemTime = stats.getLastSystemTime();
- final int iowaitTime = stats.getLastIoWaitTime();
- final int irqTime = stats.getLastIrqTime();
- final int softIrqTime = stats.getLastSoftIrqTime();
- final int idleTime = stats.getLastIdleTime();
-
- final int totalTime = userTime+systemTime+iowaitTime+irqTime+softIrqTime+idleTime;
- if (totalTime == 0) {
- return;
- }
- int userW = (userTime*W)/totalTime;
- int systemW = (systemTime*W)/totalTime;
- int irqW = ((iowaitTime+irqTime+softIrqTime)*W)/totalTime;
-
- int paddingRight = getPaddingRight();
- int x = RIGHT - paddingRight;
- int top = getPaddingTop() + 2;
- int bottom = getPaddingTop() + mFH - 2;
-
- if (irqW > 0) {
- canvas.drawRect(x-irqW, top, x, bottom, mIrqPaint);
- x -= irqW;
- }
- if (systemW > 0) {
- canvas.drawRect(x-systemW, top, x, bottom, mSystemPaint);
- x -= systemW;
- }
- if (userW > 0) {
- canvas.drawRect(x-userW, top, x, bottom, mUserPaint);
- x -= userW;
- }
-
- int y = getPaddingTop() - (int)mAscent;
- canvas.drawText(stats.mLoadText, RIGHT-paddingRight-stats.mLoadWidth-1,
- y-1, mShadowPaint);
- canvas.drawText(stats.mLoadText, RIGHT-paddingRight-stats.mLoadWidth-1,
- y+1, mShadowPaint);
- canvas.drawText(stats.mLoadText, RIGHT-paddingRight-stats.mLoadWidth+1,
- y-1, mShadow2Paint);
- canvas.drawText(stats.mLoadText, RIGHT-paddingRight-stats.mLoadWidth+1,
- y+1, mShadow2Paint);
- canvas.drawText(stats.mLoadText, RIGHT-paddingRight-stats.mLoadWidth,
- y, mLoadPaint);
-
- int N = stats.countWorkingStats();
- for (int i=0; i<N; i++) {
- CpuTracker.Stats st = stats.getWorkingStats(i);
- y += mFH;
- top += mFH;
- bottom += mFH;
-
- userW = (st.rel_utime*W)/totalTime;
- systemW = (st.rel_stime*W)/totalTime;
- x = RIGHT - paddingRight;
- if (systemW > 0) {
- canvas.drawRect(x-systemW, top, x, bottom, mSystemPaint);
- x -= systemW;
- }
- if (userW > 0) {
- canvas.drawRect(x-userW, top, x, bottom, mUserPaint);
- x -= userW;
- }
-
- canvas.drawText(st.name, RIGHT-paddingRight-st.nameWidth-1,
- y-1, mShadowPaint);
- canvas.drawText(st.name, RIGHT-paddingRight-st.nameWidth-1,
- y+1, mShadowPaint);
- canvas.drawText(st.name, RIGHT-paddingRight-st.nameWidth+1,
- y-1, mShadow2Paint);
- canvas.drawText(st.name, RIGHT-paddingRight-st.nameWidth+1,
- y+1, mShadow2Paint);
- Paint p = mLoadPaint;
- if (st.added) p = mAddedPaint;
- if (st.removed) p = mRemovedPaint;
- canvas.drawText(st.name, RIGHT-paddingRight-st.nameWidth, y, p);
- }
- }
-
- void updateDisplay() {
- final CpuTracker stats = mStats;
- final int NW = stats.countWorkingStats();
-
- int maxWidth = stats.mLoadWidth;
- for (int i=0; i<NW; i++) {
- CpuTracker.Stats st = stats.getWorkingStats(i);
- if (st.nameWidth > maxWidth) {
- maxWidth = st.nameWidth;
- }
- }
-
- int neededWidth = getPaddingLeft() + getPaddingRight() + maxWidth;
- int neededHeight = getPaddingTop() + getPaddingBottom() + (mFH*(1+NW));
- if (neededWidth != mNeededWidth || neededHeight != mNeededHeight) {
- mNeededWidth = neededWidth;
- mNeededHeight = neededHeight;
- requestLayout();
- } else {
- invalidate();
- }
- }
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- mView = new LoadView(this);
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
- WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
- PixelFormat.TRANSLUCENT);
- params.gravity = Gravity.END | Gravity.TOP;
- params.setTitle("Load Average");
- WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE);
- wm.addView(mView, params);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- ((WindowManager)getSystemService(WINDOW_SERVICE)).removeView(mView);
- mView = null;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index e300aff..99e7876 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -29,11 +29,22 @@
import android.os.UserHandle;
import android.util.Log;
+import com.android.systemui.keyboard.KeyboardUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.media.RingtonePlayer;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.power.PowerUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shortcut.ShortcutKeyDispatcher;
import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.SystemBars;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.pip.PipUI;
+import com.android.systemui.usb.StorageNotification;
+import com.android.systemui.volume.VolumeUI;
import java.util.HashMap;
import java.util.Map;
@@ -50,19 +61,20 @@
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
- com.android.systemui.tuner.TunerService.class,
- com.android.systemui.keyguard.KeyguardViewMediator.class,
- com.android.systemui.recents.Recents.class,
- com.android.systemui.volume.VolumeUI.class,
+ TunerService.class,
+ KeyguardViewMediator.class,
+ Recents.class,
+ VolumeUI.class,
Divider.class,
- com.android.systemui.statusbar.SystemBars.class,
- com.android.systemui.usb.StorageNotification.class,
- com.android.systemui.power.PowerUI.class,
- com.android.systemui.media.RingtonePlayer.class,
- com.android.systemui.keyboard.KeyboardUI.class,
- com.android.systemui.tv.pip.PipUI.class,
- com.android.systemui.shortcut.ShortcutKeyDispatcher.class,
- com.android.systemui.VendorServices.class
+ SystemBars.class,
+ StorageNotification.class,
+ PowerUI.class,
+ RingtonePlayer.class,
+ KeyboardUI.class,
+ PipUI.class,
+ ShortcutKeyDispatcher.class,
+ VendorServices.class,
+ LatencyTester.class
};
/**
@@ -70,8 +82,8 @@
* above.
*/
private final Class<?>[] SERVICES_PER_USER = new Class[] {
- com.android.systemui.recents.Recents.class,
- com.android.systemui.tv.pip.PipUI.class
+ Recents.class,
+ PipUI.class
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 261d241..6c35243 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -43,6 +43,7 @@
import android.util.Log;
import android.view.Display;
+import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.SystemUIApplication;
@@ -60,6 +61,11 @@
private static final String ACTION_BASE = "com.android.systemui.doze";
private static final String PULSE_ACTION = ACTION_BASE + ".pulse";
+ /**
+ * If true, reregisters all trigger sensors when the screen turns off.
+ */
+ private static final boolean REREGISTER_ALL_SENSORS_ON_SCREEN_OFF = true;
+
private final String mTag = String.format(TAG + ".%08x", hashCode());
private final Context mContext = this;
private final DozeParameters mDozeParameters = new DozeParameters(mContext);
@@ -80,6 +86,8 @@
private boolean mCarMode;
private long mNotificationPulseTime;
+ private AmbientDisplayConfiguration mConfig;
+
public DozeService() {
if (DEBUG) Log.d(mTag, "new DozeService()");
setDebug(DEBUG);
@@ -120,6 +128,7 @@
setWindowless(true);
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ mConfig = new AmbientDisplayConfiguration(mContext);
mSensors = new TriggerSensor[] {
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
@@ -130,12 +139,12 @@
mPickupSensor = new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
Settings.Secure.DOZE_PULSE_ON_PICK_UP,
- mDozeParameters.getPulseOnPickup(), mDozeParameters.getVibrateOnPickup(),
+ mConfig.pulseOnPickupAvailable(), mDozeParameters.getVibrateOnPickup(),
DozeLog.PULSE_REASON_SENSOR_PICKUP),
new TriggerSensor(
- findSensorWithType(mDozeParameters.getDoubleTapSensorType()),
- Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
- mDozeParameters.getPulseOnPickup(), mDozeParameters.getVibrateOnPickup(),
+ findSensorWithType(mConfig.doubleTapSensorType()),
+ Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, true,
+ mDozeParameters.getVibrateOnPickup(),
DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP)
};
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -272,6 +281,9 @@
public void onPulseFinished() {
if (mPulsing && mDreaming) {
mPulsing = false;
+ if (REREGISTER_ALL_SENSORS_ON_SCREEN_OFF) {
+ reregisterAllSensors();
+ }
turnDisplayOff();
}
mWakeLock.release(); // needs to be unconditional to balance acquire
@@ -308,6 +320,15 @@
listenForNotifications(listen);
}
+ private void reregisterAllSensors() {
+ for (TriggerSensor s : mSensors) {
+ s.setListening(false);
+ }
+ for (TriggerSensor s : mSensors) {
+ s.setListening(true);
+ }
+ }
+
private void listenForBroadcasts(boolean listen) {
if (listen) {
final IntentFilter filter = new IntentFilter(PULSE_ACTION);
@@ -342,7 +363,7 @@
private void requestNotificationPulse() {
if (DEBUG) Log.d(mTag, "requestNotificationPulse");
- if (!mDozeParameters.getPulseOnNotifications()) return;
+ if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return;
mNotificationPulseTime = SystemClock.elapsedRealtime();
requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b9e8acb..24247e4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -73,6 +73,7 @@
import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.LatencyTracker;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
@@ -1894,6 +1895,9 @@
private void handleNotifyScreenTurnedOn() {
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(LatencyTracker.ACTION_TURN_ON_SCREEN);
+ }
synchronized (this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
mStatusBarKeyguardViewManager.onScreenTurnedOn();
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..f9a4f7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.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.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() {
+ mTouchHandler.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..ffe99c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -0,0 +1,475 @@
+/*
+ * 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.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 com.android.internal.os.BackgroundThread;
+import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.tuner.TunerService;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler implements TunerService.Tunable {
+ private static final String TAG = "PipTouchHandler";
+ private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false;
+
+ private static final String TUNER_KEY_SWIPE_TO_DISMISS = "pip_swipe_to_dismiss";
+ private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss";
+
+ 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 final Context mContext;
+ private final IActivityManager mActivityManager;
+ private final ViewConfiguration mViewConfig;
+ private final InputChannel mInputChannel = new InputChannel();
+
+ private final PipInputEventReceiver mInputEventReceiver;
+ private PipDismissViewController mDismissViewController;
+ private PipSnapAlgorithm mSnapAlgorithm;
+
+ private boolean mEnableSwipeToDismiss = true;
+ private boolean mEnableDragToDismiss = true;
+
+ 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 boolean mIsSwipingToDismiss;
+ private int mActivePointerId;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ 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());
+ if (mEnableDragToDismiss) {
+ mDismissViewController = new PipDismissViewController(context);
+ }
+ mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+
+ // Register any tuner settings changes
+ TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS,
+ TUNER_KEY_DRAG_TO_DISMISS);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ switch (key) {
+ case TUNER_KEY_SWIPE_TO_DISMISS:
+ mEnableSwipeToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0;
+ break;
+ case TUNER_KEY_DRAG_TO_DISMISS:
+ mEnableDragToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0;
+ break;
+ }
+ }
+
+ public void onConfigurationChanged() {
+ updateBoundedPinnedStackBounds();
+ }
+
+ 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;
+ if (mEnableDragToDismiss) {
+ // 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);
+ float x = ev.getX(activePointerIndex);
+ float y = ev.getY(activePointerIndex);
+ float left = mPinnedStackBounds.left + (x - mLastTouch.x);
+ float top = mPinnedStackBounds.top + (y - mLastTouch.y);
+
+ if (!mIsDragging) {
+ // Check if the pointer has moved far enough
+ float movement = PointF.length(mDownTouch.x - x, mDownTouch.y - y);
+ if (movement > mViewConfig.getScaledTouchSlop()) {
+ mIsDragging = true;
+ if (mEnableSwipeToDismiss) {
+ // TODO: this check can have some buffer so that we only start swiping
+ // after a significant move out of bounds
+ mIsSwipingToDismiss = !(mBoundedPinnedStackBounds.left <= left &&
+ left <= mBoundedPinnedStackBounds.right) &&
+ Math.abs(mDownTouch.x - x) > Math.abs(y - mLastTouch.y);
+ }
+ if (mEnableDragToDismiss) {
+ mDismissViewController.showDismissTarget();
+ }
+ }
+ }
+
+ if (mIsSwipingToDismiss) {
+ // Ignore the vertical movement
+ top = mPinnedStackBounds.top;
+ movePinnedStack(left, top);
+ } else if (mIsDragging) {
+ // Move the pinned stack
+ if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
+ left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
+ mBoundedPinnedStackBounds.right, left));
+ top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
+ mBoundedPinnedStackBounds.bottom, top));
+ }
+ 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);
+ mVelocityTracker.computeCurrentVelocity(1000,
+ ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+ float velocityX = mVelocityTracker.getXVelocity();
+ float velocityY = mVelocityTracker.getYVelocity();
+ float velocity = PointF.length(velocityX, velocityY);
+
+ if (mIsSwipingToDismiss) {
+ if (Math.abs(velocityX) > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ flingToDismiss(velocityX);
+ } else {
+ animateToClosestSnapTarget();
+ }
+ } else if (mIsDragging) {
+ 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 = mEnableDragToDismiss
+ ? mDismissViewController.getDismissBounds()
+ : null;
+ if (dismissBounds != null && dismissBounds.contains(x, y)) {
+ animateDismissPinnedStack(dismissBounds);
+ } else {
+ animateToClosestSnapTarget();
+ }
+ }
+ } else {
+ expandPinnedStackToFullscreen();
+ }
+ if (mEnableDragToDismiss) {
+ mDismissViewController.destroyDismissTarget();
+ }
+
+ // Fall through to clean up
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ mIsDragging = false;
+ mIsSwipingToDismiss = 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;
+ }
+ }
+
+ /**
+ * Flings the PIP to the closest snap target.
+ */
+ private void flingToSnapTarget(float velocity, float velocityX, float velocityY) {
+ Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
+ mPinnedStackBounds, velocityX, velocityY);
+ if (!mPinnedStackBounds.equals(toBounds)) {
+ mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+ toBounds, 0, FAST_OUT_SLOW_IN);
+ mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
+ distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
+ velocity);
+ mPinnedStackBoundsAnimator.start();
+ }
+ }
+
+ /**
+ * Animates the PIP to the closest snap target.
+ */
+ private void animateToClosestSnapTarget() {
+ Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
+ mPinnedStackBounds);
+ if (!mPinnedStackBounds.equals(toBounds)) {
+ mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+ toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN);
+ mPinnedStackBoundsAnimator.start();
+ }
+ }
+
+ /**
+ * Flings the PIP to dismiss it offscreen.
+ */
+ private void flingToDismiss(float velocityX) {
+ float offsetX = velocityX > 0
+ ? mBoundedPinnedStackBounds.right + 2 * mPinnedStackBounds.width()
+ : mBoundedPinnedStackBounds.left - 2 * mPinnedStackBounds.width();
+ Rect toBounds = new Rect(mPinnedStackBounds);
+ toBounds.offsetTo((int) offsetX, toBounds.top);
+ if (!mPinnedStackBounds.equals(toBounds)) {
+ mPinnedStackBoundsAnimator = createResizePinnedStackAnimation(
+ toBounds, 0, FAST_OUT_SLOW_IN);
+ mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0,
+ distanceBetweenRectOffsets(mPinnedStackBounds, toBounds),
+ velocityX);
+ 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();
+ }
+ }
+
+ /**
+ * Animates the dismissal of the PIP over the dismiss target 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);
+ if (info != null) {
+ mPinnedStackBounds.set(info.bounds);
+ mBoundedPinnedStackBounds.set(mActivityManager.getPictureInPictureMovementBounds(
+ info.displayId));
+ mSnapAlgorithm = new PipSnapAlgorithm(mContext, info.displayId);
+ }
+ } 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 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 97%
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 085e003..894bc53 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -14,12 +14,11 @@
* 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;
import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -38,18 +37,16 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-
+import android.view.Display;
+import android.view.IWindowManager;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.statusbar.tv.TvStatusBar;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
@@ -64,8 +61,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.
@@ -225,8 +220,12 @@
private void loadConfigurationsAndApply() {
Resources res = mContext.getResources();
- mDefaultPipBounds = Rect.unflattenFromString(res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureBounds));
+ try {
+ mDefaultPipBounds = mActivityManager.getDefaultPictureInPictureBounds(
+ Display.DEFAULT_DISPLAY);
+ } 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(
@@ -248,7 +247,7 @@
/**
* Updates the PIP per configuration changed.
*/
- void onConfigurationChanged() {
+ public void onConfigurationChanged() {
loadConfigurationsAndApply();
mPipRecentsOverlayManager.onConfigurationChanged(mContext);
}
@@ -298,6 +297,7 @@
mListeners.get(i).onMoveToFullscreen();
}
resizePinnedStack(mState);
+ updatePipVisibility(false);
}
/**
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/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index afedbe3..1c242e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -88,9 +88,9 @@
if (mListening == listening) return;
mListening = listening;
if (mListening) {
- mPages.get(mPosition).setListening(listening);
+ setPageListening(mPosition, true);
if (mOffPage) {
- mPages.get(mPosition + 1).setListening(listening);
+ setPageListening(mPosition + 1, true);
}
} else {
// Make sure no pages are listening.
@@ -131,6 +131,9 @@
private void setPageListening(int position, boolean listening) {
if (position >= mPages.size()) return;
+ if (isLayoutRtl()) {
+ position = mPages.size() - 1 - position;
+ }
mPages.get(position).setListening(listening);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 2173922..f345172 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -334,4 +334,10 @@
public int getQsMinExpansionHeight() {
return mHeader.getHeight();
}
+
+ @Override
+ public void hideImmediately() {
+ animate().cancel();
+ setY(-mHeader.getHeight());
+ }
}
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/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index e1db8c6..c699e27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -219,7 +219,7 @@
}
public void onCollapse() {
- if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
+ if (mCustomizePanel != null && mCustomizePanel.isShown()) {
mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
}
}
@@ -404,7 +404,7 @@
}
public void closeDetail() {
- if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
+ if (mCustomizePanel != null && mCustomizePanel.isShown()) {
// Treat this as a detail panel for now, to make things easy.
mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 3493d24..cf2c16d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -163,6 +163,10 @@
}
}
+ public boolean isShown() {
+ return isShown;
+ }
+
private void setCustomizing(boolean customizing) {
mCustomizing = customizing;
mQsContainer.notifyCustomizeChanged();
@@ -217,7 +221,9 @@
private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- setCustomizing(true);
+ if (isShown) {
+ setCustomizing(true);
+ }
mNotifQsContainer.setCustomizerAnimating(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 40ef6eb..0cd6490 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -64,7 +64,10 @@
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
final QSTile<?> tile = host.createTile(spec);
- if (tile == null || !tile.isAvailable()) {
+ if (tile == null) {
+ continue;
+ } else if (!tile.isAvailable()) {
+ tile.destroy();
continue;
}
tile.setListening(this, true);
@@ -78,6 +81,7 @@
tile.getState().copyTo(state);
// Ignore the current state and get the generic label instead.
state.label = tile.getTileLabel();
+ tile.destroy();
mainHandler.post(new Runnable() {
@Override
public void run() {
@@ -126,6 +130,7 @@
state.label = label;
state.contentDescription = label;
state.icon = new DrawableIcon(drawable);
+ state.autoMirrorDrawable = false;
addTile(spec, appLabel, state, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index b36221d..484e008 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -221,7 +221,9 @@
@Override
public State newTileState() {
- return new State();
+ State state = new State();
+ state.autoMirrorDrawable = false;
+ return state;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index b61a81c..d89fbfd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -118,7 +118,7 @@
@Override
public Drawable getDrawable(Context context) {
BatteryMeterDrawable drawable =
- new BatteryMeterDrawable(context, new Handler(Looper.getMainLooper()),
+ new BatteryMeterDrawable(context,
context.getColor(R.color.batterymeter_frame_color));
drawable.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
drawable.onPowerSaveChanged(mPowerSave);
@@ -165,7 +165,7 @@
private final class BatteryDetail implements DetailAdapter, OnClickListener,
OnAttachStateChangeListener {
private final BatteryMeterDrawable mDrawable = new BatteryMeterDrawable(mHost.getContext(),
- new Handler(), mHost.getContext().getColor(R.color.batterymeter_frame_color));
+ mHost.getContext().getColor(R.color.batterymeter_frame_color));
private View mCurrentView;
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 89bb1d2..ec4ab51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -69,12 +69,23 @@
private boolean mListening;
private boolean mShowingDetail;
+ private boolean mReceiverRegistered;
public DndTile(Host host) {
super(host);
mController = host.getZenModeController();
mDetailAdapter = new DndDetailAdapter();
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
+ mReceiverRegistered = true;
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ if (mReceiverRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mReceiverRegistered = false;
+ }
}
public static void setVisible(Context context, boolean visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 9415b27..c02e5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -75,14 +75,12 @@
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean isActivated = mController.isActivated();
state.value = isActivated;
- state.label = mContext.getString(R.string.quick_settings_night_display_label);
+ state.label = state.contentDescription =
+ mContext.getString(R.string.quick_settings_night_display_label);
state.icon = ResourceIcon.get(isActivated ? R.drawable.ic_qs_night_display_on
: R.drawable.ic_qs_night_display_off);
- state.contentDescription = mContext.getString(isActivated
- ? R.string.quick_settings_night_display_summary_on
- : R.string.quick_settings_night_display_summary_off);
- state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
- = Switch.class.getName();
+ state.minimalAccessibilityClassName = state.expandedAccessibilityClassName =
+ Switch.class.getName();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 7207463..3c245b4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -18,12 +18,10 @@
import android.app.ActivityManager;
import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -99,7 +97,7 @@
// and does not reside in the home stack.
private String mOverrideRecentsPackageName;
- private Handler mHandler = new Handler();
+ private Handler mHandler;
private RecentsImpl mImpl;
private int mDraggingInRecentsCurrentUser;
@@ -165,20 +163,6 @@
}
};
-
- private BroadcastReceiver mSystemUserUnlockedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId != UserHandle.USER_NULL) {
- mImpl.onUserUnlocked(userId);
- }
- }
- }
- };
-
-
/**
* Returns the callbacks interface that non-system users can call.
*/
@@ -208,7 +192,7 @@
sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
sTaskLoader = new RecentsTaskLoader(mContext);
sConfiguration = new RecentsConfiguration(mContext);
-
+ mHandler = new Handler();
UiModeManager uiModeManager = (UiModeManager) mContext.
getSystemService(Context.UI_MODE_SERVICE);
if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
@@ -238,12 +222,6 @@
// For the system user, initialize an instance of the interface that we can pass to the
// secondary user
mSystemToUserCallbacks = new RecentsSystemUser(mContext, mImpl);
-
- // Listen for user-unlocked to kick off preloading recents
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiverAsUser(mSystemUserUnlockedReceiver, UserHandle.SYSTEM, filter,
- null /* permission */, null /* scheduler */);
} else {
// For the secondary user, bind to the primary user's service to get a persistent
// interface to register its implementation and to later update its state
@@ -501,6 +479,7 @@
return COUNTER_WINDOW_UNSUPPORTED;
case ActivityInfo.RESIZE_MODE_RESIZEABLE:
case ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE:
+ case ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
return COUNTER_WINDOW_SUPPORTED;
default:
return COUNTER_WINDOW_INCOMPATIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index e5493b6..ec99d20 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -43,7 +43,7 @@
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
-import com.android.systemui.LatencyTracker;
+import com.android.keyguard.LatencyTracker;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 390ef87..64ef997 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -188,7 +188,7 @@
reloadResources();
}
- public void onUserUnlocked(int userId) {
+ public void onBootCompleted() {
// When we start, preload the data associated with the previous recent tasks.
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
@@ -201,10 +201,6 @@
loader.loadTasks(mContext, plan, launchOpts);
}
- public void onBootCompleted() {
- // Do nothing
- }
-
public void onConfigurationChanged() {
Resources res = mContext.getResources();
reloadResources();
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..6a1b477 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;
@@ -1082,7 +1084,8 @@
if (mWm == null) return;
try {
- WindowManagerGlobal.getWindowManagerService().getStableInsets(outStableInsets);
+ WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY,
+ outStableInsets);
} catch (Exception e) {
e.printStackTrace();
}
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/tv/animations/HomeRecentsEnterExitAnimationHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/HomeRecentsEnterExitAnimationHolder.java
index 9faaa4b..a673c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/HomeRecentsEnterExitAnimationHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/HomeRecentsEnterExitAnimationHolder.java
@@ -47,12 +47,13 @@
public void startEnterAnimation(boolean isPipShown) {
for(int i = 0; i < mGridView.getChildCount(); i++) {
TaskCardView view = (TaskCardView) mGridView.getChildAt(i);
+ long delay = Math.max(mDelay * i, 0);
view.setTranslationX(-mTranslationX);
view.animate()
.alpha(isPipShown ? mDimAlpha : 1.0f)
.translationX(0)
.setDuration(mDuration)
- .setStartDelay(mDelay * i)
+ .setStartDelay(delay)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
}
}
@@ -60,11 +61,12 @@
public void startExitAnimation(DismissRecentsToHomeAnimationStarted dismissEvent) {
for(int i = mGridView.getChildCount() - 1; i >= 0; i--) {
TaskCardView view = (TaskCardView) mGridView.getChildAt(i);
+ long delay = Math.max(mDelay * (mGridView.getChildCount() - 1 - i), 0);
view.animate()
.alpha(0.0f)
.translationXBy(-mTranslationX)
.setDuration(mDuration)
- .setStartDelay(mDelay * (mGridView.getChildCount() - 1 - i))
+ .setStartDelay(delay)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
if(i == 0) {
view.animate().setListener(dismissEvent.getAnimationTrigger()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 24ef433..febeacb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -95,7 +95,6 @@
private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
- private TaskStack mStack;
private TaskStackView mTaskStackView;
private TextView mStackActionButton;
private TextView mEmptyView;
@@ -195,7 +194,6 @@
* Called from RecentsActivity when the task stack is updated.
*/
public void updateStack(TaskStack stack, boolean setStackViewTasks) {
- mStack = stack;
if (setStackViewTasks) {
mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
}
@@ -212,7 +210,7 @@
* Returns the current TaskStack.
*/
public TaskStack getStack() {
- return mStack;
+ return mTaskStackView.getStack();
}
/*
@@ -251,8 +249,7 @@
/** Launches the task that recents was launched from if possible */
public boolean launchPreviousTask() {
if (mTaskStackView != null) {
- TaskStack stack = mTaskStackView.getStack();
- Task task = stack.getLaunchTarget();
+ Task task = getStack().getLaunchTarget();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
@@ -437,8 +434,9 @@
public final void onBusEvent(LaunchTaskEvent event) {
mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
- mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView,
- event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack);
+ mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
+ event.taskView, event.screenPinningRequested, event.targetTaskBounds,
+ event.targetTaskStack);
}
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
@@ -514,8 +512,7 @@
EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
// Remove the task and don't bother relaying out, as all the tasks will be
// relaid out when the stack changes on the multiwindow change event
- mTaskStackView.getStack().removeTask(event.task, null,
- true /* fromDockGesture */);
+ getStack().removeTask(event.task, null, true /* fromDockGesture */);
}
};
@@ -536,7 +533,7 @@
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
} else {
- EventBus.getDefault().send(new DragEndCancelledEvent(mStack, event.task,
+ EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
event.taskView));
}
} else {
@@ -598,7 +595,7 @@
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
- && mStack.getTaskCount() > 0) {
+ && getStack().getTaskCount() > 0) {
animateBackgroundScrim(1f,
TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
}
@@ -797,8 +794,8 @@
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
- if (mStack != null) {
- mStack.dump(innerPrefix, writer);
+ if (getStack() != null) {
+ getStack().dump(innerPrefix, writer);
}
if (mTaskStackView != null) {
mTaskStackView.dump(innerPrefix, writer);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 265f319..d3ec984 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -31,6 +31,7 @@
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -139,7 +140,10 @@
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
- return mDragRequested;
+ if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getStackTaskCount() == 0) {
+ EventBus.getDefault().send(new HideRecentsEvent(false, true));
+ }
+ return true;
}
/**** Events ****/
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/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index cb77d7b..4e34bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1113,6 +1113,7 @@
public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
+ && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
&& getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
mState.growAfterRecentsDrawn = true;
startDragging(false /* animate */, false /* touching */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a7132e5..4cc7a16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -161,7 +161,7 @@
"com.android.systemui.statusbar.banner_action_cancel";
private static final String BANNER_ACTION_SETUP =
"com.android.systemui.statusbar.banner_action_setup";
- private static final String WORK_CHALLENGE_UNLOCKED_NOTIFICATION_ACTION
+ private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
= "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
protected CommandQueue mCommandQueue;
@@ -217,14 +217,14 @@
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
// public mode, private notifications, etc
- private boolean mLockscreenPublicMode = false;
+ private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
private UserManager mUserManager;
private int mDensity;
- private KeyguardManager mKeyguardManager;
+ protected KeyguardManager mKeyguardManager;
private LockPatternUtils mLockPatternUtils;
// UI-specific methods
@@ -465,11 +465,11 @@
row.setUserExpanded(true);
if (!mAllowLockscreenRemoteInput) {
- if (isLockscreenPublicMode()) {
+ final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+ if (isLockscreenPublicMode(userId)) {
onLockedRemoteInput(row, view);
return true;
}
- final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
if (mUserManager.getUserInfo(userId).isManagedProfile()
&& mKeyguardManager.isDeviceLocked(userId)) {
onLockedWorkRemoteInput(userId, row, view);
@@ -560,7 +560,7 @@
);
}
- } else if (WORK_CHALLENGE_UNLOCKED_NOTIFICATION_ACTION.equals(action)) {
+ } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
if (intentSender != null) {
@@ -577,7 +577,6 @@
/* ignore */
}
}
- onWorkChallengeUnlocked();
}
}
};
@@ -585,12 +584,18 @@
private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
isCurrentProfile(getSendingUserId())) {
mUsersAllowingPrivateNotifications.clear();
updateLockscreenNotificationSetting();
updateNotifications();
+ } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
+ if (userId != mCurrentUserId && isCurrentProfile(userId)) {
+ onWorkChallengeChanged();
+ }
}
}
};
@@ -815,7 +820,7 @@
mContext.registerReceiver(mBroadcastReceiver, filter);
IntentFilter internalFilter = new IntentFilter();
- internalFilter.addAction(WORK_CHALLENGE_UNLOCKED_NOTIFICATION_ACTION);
+ internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
internalFilter.addAction(BANNER_ACTION_CANCEL);
internalFilter.addAction(BANNER_ACTION_SETUP);
mContext.registerReceiver(mBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
@@ -823,6 +828,7 @@
IntentFilter allUsersFilter = new IntentFilter();
allUsersFilter.addAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
null, null);
updateCurrentProfilesCache();
@@ -1126,9 +1132,10 @@
@Override
public void onClick(View v) {
// If the user has security enabled, show challenge if the setting is changed.
- if (guts.hasImportanceChanged() && isLockscreenPublicMode() &&
- (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
+ if (guts.hasImportanceChanged()
+ && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+ && (mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
OnDismissAction dismissAction = new OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -1430,15 +1437,15 @@
/**
* Save the current "public" (locked and secure) state of the lockscreen.
*/
- public void setLockscreenPublicMode(boolean publicMode) {
- mLockscreenPublicMode = publicMode;
+ public void setLockscreenPublicMode(boolean publicMode, int userId) {
+ mLockscreenPublicMode.put(userId, publicMode);
}
- public boolean isLockscreenPublicMode() {
- return mLockscreenPublicMode;
+ public boolean isLockscreenPublicMode(int userId) {
+ return mLockscreenPublicMode.get(userId, false);
}
- protected void onWorkChallengeUnlocked() {}
+ protected void onWorkChallengeChanged() {}
/**
* Has the given user chosen to allow notifications to be shown even when the lockscreen is in
@@ -1496,8 +1503,9 @@
* If so, notifications should be hidden.
*/
@Override // NotificationData.Environment
- public boolean shouldHideNotifications(int userid) {
- return isLockscreenPublicMode() && !userAllowsNotificationsInPublic(userid);
+ public boolean shouldHideNotifications(int userId) {
+ return isLockscreenPublicMode(mCurrentUserId) && !userAllowsNotificationsInPublic(userId)
+ || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
}
/**
@@ -1506,7 +1514,7 @@
*/
@Override // NotificationDate.Environment
public boolean shouldHideNotifications(String key) {
- return isLockscreenPublicMode()
+ return isLockscreenPublicMode(mCurrentUserId)
&& mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
}
@@ -1514,8 +1522,8 @@
* Returns true if we're on a secure lockscreen.
*/
@Override // NotificationData.Environment
- public boolean onSecureLockScreen() {
- return isLockscreenPublicMode();
+ public boolean isSecurelyLocked(int userId) {
+ return isLockscreenPublicMode(userId);
}
public void onNotificationClear(StatusBarNotification notification) {
@@ -1711,6 +1719,23 @@
sbn.getPackageContext(mContext),
contentContainerPublic, mOnClickHandler);
}
+
+ if (contentViewLocal != null) {
+ contentViewLocal.setIsRootNamespace(true);
+ contentContainer.setContractedChild(contentViewLocal);
+ }
+ if (bigContentViewLocal != null) {
+ bigContentViewLocal.setIsRootNamespace(true);
+ contentContainer.setExpandedChild(bigContentViewLocal);
+ }
+ if (headsUpContentViewLocal != null) {
+ headsUpContentViewLocal.setIsRootNamespace(true);
+ contentContainer.setHeadsUpChild(headsUpContentViewLocal);
+ }
+ if (publicViewLocal != null) {
+ publicViewLocal.setIsRootNamespace(true);
+ contentContainerPublic.setContractedChild(publicViewLocal);
+ }
}
catch (RuntimeException e) {
final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
@@ -1718,23 +1743,6 @@
return false;
}
- if (contentViewLocal != null) {
- contentViewLocal.setIsRootNamespace(true);
- contentContainer.setContractedChild(contentViewLocal);
- }
- if (bigContentViewLocal != null) {
- bigContentViewLocal.setIsRootNamespace(true);
- contentContainer.setExpandedChild(bigContentViewLocal);
- }
- if (headsUpContentViewLocal != null) {
- headsUpContentViewLocal.setIsRootNamespace(true);
- contentContainer.setHeadsUpChild(headsUpContentViewLocal);
- }
- if (publicViewLocal != null) {
- publicViewLocal.setIsRootNamespace(true);
- contentContainerPublic.setContractedChild(publicViewLocal);
- }
-
// Extract target SDK version.
try {
ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
@@ -2076,8 +2084,7 @@
if (newIntent == null) {
return false;
}
- final Intent callBackIntent = new Intent(
- WORK_CHALLENGE_UNLOCKED_NOTIFICATION_ACTION);
+ final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
callBackIntent.setPackage(mContext.getPackageName());
@@ -2276,14 +2283,16 @@
entry.row.setOnKeyguard(false);
entry.row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
}
+ int userId = entry.notification.getUserId();
boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
entry.notification) && !entry.row.isRemoved();
boolean childWithVisibleSummary = childNotification
&& mGroupManager.getGroupSummary(entry.notification).getVisibility()
== View.VISIBLE;
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
- if (suppressedSummary || (isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
- (onKeyguard && !childWithVisibleSummary
+ if (suppressedSummary
+ || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
+ || (onKeyguard && !childWithVisibleSummary
&& (visibleNotifications >= maxNotifications || !showOnKeyguard))) {
entry.row.setVisibility(View.GONE);
if (onKeyguard && showOnKeyguard && !childNotification && !suppressedSummary) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index e781f1b..f438762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -488,9 +488,9 @@
final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
if (messagingIcon != null) {
keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_im),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
messagingIcon,
- KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_S,
KeyEvent.META_META_ON));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index cf962df..0ef97152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -95,8 +95,6 @@
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor);
context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
new IntentFilter(Intent.ACTION_TIME_TICK), null, null);
- context.registerReceiverAsUser(mUnlockReceiver, UserHandle.ALL,
- new IntentFilter(Intent.ACTION_USER_UNLOCKED), null, null);
}
public void setVisible(boolean visible) {
@@ -322,6 +320,13 @@
super.onFingerprintAuthFailed();
mLastSuccessiveErrorMessage = -1;
}
+
+ @Override
+ public void onUserUnlocked() {
+ if (mVisible) {
+ updateIndication();
+ }
+ }
};
BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
@@ -333,14 +338,6 @@
}
};
- BroadcastReceiver mUnlockReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mVisible) {
- updateIndication();
- }
- }
- };
private final Handler mHandler = new Handler() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 78e56c0..58d57f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -116,6 +116,8 @@
private boolean mForceSelectNextLayout = true;
private PendingIntent mPreviousExpandedRemoteInputIntent;
private PendingIntent mPreviousHeadsUpRemoteInputIntent;
+ private RemoteInputView mCachedExpandedRemoteInput;
+ private RemoteInputView mCachedHeadsUpRemoteInput;
private int mContentHeightAtAnimationStart = UNDEFINED;
private boolean mFocusOnVisibilityChange;
@@ -298,6 +300,9 @@
mExpandedRemoteInput.onNotificationUpdateOrReset();
if (mExpandedRemoteInput.isActive()) {
mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
+ mCachedExpandedRemoteInput = mExpandedRemoteInput;
+ mExpandedRemoteInput.dispatchStartTemporaryDetach();
+ ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
}
}
if (mExpandedChild != null) {
@@ -310,6 +315,9 @@
mHeadsUpRemoteInput.onNotificationUpdateOrReset();
if (mHeadsUpRemoteInput.isActive()) {
mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
+ mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
+ mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
+ ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
}
}
if (mHeadsUpChild != null) {
@@ -963,22 +971,35 @@
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
- mPreviousExpandedRemoteInputIntent);
+ mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput);
} else {
mExpandedRemoteInput = null;
}
+ if (mCachedExpandedRemoteInput != null
+ && mCachedExpandedRemoteInput != mExpandedRemoteInput) {
+ // We had a cached remote input but didn't reuse it. Clean up required.
+ mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach();
+ }
+ mCachedExpandedRemoteInput = null;
View headsUpContentView = mHeadsUpChild;
if (headsUpContentView != null) {
mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
- mPreviousHeadsUpRemoteInputIntent);
+ mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput);
} else {
mHeadsUpRemoteInput = null;
}
+ if (mCachedHeadsUpRemoteInput != null
+ && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) {
+ // We had a cached remote input but didn't reuse it. Clean up required.
+ mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach();
+ }
+ mCachedHeadsUpRemoteInput = null;
}
private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
- boolean hasRemoteInput, PendingIntent existingPendingIntent) {
+ boolean hasRemoteInput, PendingIntent existingPendingIntent,
+ RemoteInputView cachedView) {
View actionContainerCandidate = view.findViewById(
com.android.internal.R.id.actions_container);
if (actionContainerCandidate instanceof FrameLayout) {
@@ -991,15 +1012,22 @@
if (existing == null && hasRemoteInput) {
ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
- RemoteInputView riv = RemoteInputView.inflate(
- mContext, actionContainer, entry, mRemoteInputController);
+ if (cachedView == null) {
+ RemoteInputView riv = RemoteInputView.inflate(
+ mContext, actionContainer, entry, mRemoteInputController);
- riv.setVisibility(View.INVISIBLE);
- actionContainer.addView(riv, new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT)
- );
- existing = riv;
+ riv.setVisibility(View.INVISIBLE);
+ actionContainer.addView(riv, new LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT)
+ );
+ existing = riv;
+ } else {
+ actionContainer.addView(cachedView);
+ cachedView.dispatchFinishTemporaryDetach();
+ cachedView.requestFocus();
+ existing = cachedView;
+ }
}
if (hasRemoteInput) {
int color = entry.notification.getNotification().color;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index b6e54af..bae16f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -380,7 +380,7 @@
return true;
}
- if (mEnvironment.onSecureLockScreen() &&
+ if (mEnvironment.isSecurelyLocked(sbn.getUserId()) &&
(sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
|| mEnvironment.shouldHideNotifications(sbn.getUserId())
|| mEnvironment.shouldHideNotifications(sbn.getKey()))) {
@@ -463,7 +463,7 @@
* Provides access to keyguard state and user settings dependent data.
*/
public interface Environment {
- public boolean onSecureLockScreen();
+ public boolean isSecurelyLocked(int userId);
public boolean shouldHideNotifications(int userid);
public boolean shouldHideNotifications(String key);
public boolean isDeviceProvisioned();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 6cbacea..66cc15d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -21,7 +21,9 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -31,8 +33,9 @@
*/
public class RemoteInputController {
- private final ArrayList<WeakReference<NotificationData.Entry>> mOpen = new ArrayList<>();
- private final ArraySet<String> mSpinning = new ArraySet<>();
+ private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
+ = new ArrayList<>();
+ private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
private final HeadsUpManager mHeadsUpManager;
@@ -41,36 +44,72 @@
mHeadsUpManager = headsUpManager;
}
- public void addRemoteInput(NotificationData.Entry entry) {
+ /**
+ * Adds a currently active remote input.
+ *
+ * @param entry the entry for which a remote input is now active.
+ * @param token a token identifying the view that is managing the remote input
+ */
+ public void addRemoteInput(NotificationData.Entry entry, Object token) {
Preconditions.checkNotNull(entry);
+ Preconditions.checkNotNull(token);
boolean found = pruneWeakThenRemoveAndContains(
- entry /* contains */, null /* remove */);
+ entry /* contains */, null /* remove */, token /* removeToken */);
if (!found) {
- mOpen.add(new WeakReference<>(entry));
+ mOpen.add(new Pair<>(new WeakReference<>(entry), token));
}
apply(entry);
}
- public void removeRemoteInput(NotificationData.Entry entry) {
+ /**
+ * Removes a currently active remote input.
+ *
+ * @param entry the entry for which a remote input should be removed.
+ * @param token a token identifying the view that is requesting the removal. If non-null,
+ * the entry is only removed if the token matches the last added token for this
+ * entry. If null, the entry is removed regardless.
+ */
+ public void removeRemoteInput(NotificationData.Entry entry, Object token) {
Preconditions.checkNotNull(entry);
- pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */);
+ pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
apply(entry);
}
- public void addSpinning(String key) {
- mSpinning.add(key);
+ /**
+ * Adds a currently spinning (i.e. sending) remote input.
+ *
+ * @param key the key of the entry that's spinning.
+ * @param token the token of the view managing the remote input.
+ */
+ public void addSpinning(String key, Object token) {
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(token);
+
+ mSpinning.put(key, token);
}
- public void removeSpinning(String key) {
- mSpinning.remove(key);
+ /**
+ * Removes a currently spinning remote input.
+ *
+ * @param key the key of the entry for which a remote input should be removed.
+ * @param token a token identifying the view that is requesting the removal. If non-null,
+ * the entry is only removed if the token matches the last added token for this
+ * entry. If null, the entry is removed regardless.
+ */
+ public void removeSpinning(String key, Object token) {
+ Preconditions.checkNotNull(key);
+
+ if (token == null || mSpinning.get(key) == token) {
+ mSpinning.remove(key);
+ }
}
public boolean isSpinning(String key) {
- return mSpinning.contains(key);
+ return mSpinning.containsKey(key);
}
private void apply(NotificationData.Entry entry) {
@@ -86,14 +125,16 @@
* @return true if {@param entry} has an active RemoteInput
*/
public boolean isRemoteInputActive(NotificationData.Entry entry) {
- return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */);
+ return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */,
+ null /* removeToken */);
}
/**
* @return true if any entry has an active RemoteInput
*/
public boolean isRemoteInputActive() {
- pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */);
+ pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */,
+ null /* removeToken */);
return !mOpen.isEmpty();
}
@@ -101,17 +142,27 @@
* Prunes dangling weak references, removes entries referring to {@param remove} and returns
* whether {@param contains} is part of the array in a single loop.
* @param remove if non-null, removes this entry from the active remote inputs
+ * @param removeToken if non-null, only removes an entry if this matches the token when the
+ * entry was added.
* @return true if {@param contains} is in the set of active remote inputs
*/
private boolean pruneWeakThenRemoveAndContains(
- NotificationData.Entry contains, NotificationData.Entry remove) {
+ NotificationData.Entry contains, NotificationData.Entry remove, Object removeToken) {
boolean found = false;
for (int i = mOpen.size() - 1; i >= 0; i--) {
- NotificationData.Entry item = mOpen.get(i).get();
- if (item == null || item == remove) {
+ NotificationData.Entry item = mOpen.get(i).first.get();
+ Object itemToken = mOpen.get(i).second;
+ boolean removeTokenMatches = (removeToken == null || itemToken == removeToken);
+
+ if (item == null || (item == remove && removeTokenMatches)) {
mOpen.remove(i);
} else if (item == contains) {
- found = true;
+ if (removeToken != null && removeToken != itemToken) {
+ // We need to update the token. Remove here and let caller reinsert it.
+ mOpen.remove(i);
+ } else {
+ found = true;
+ }
}
}
return found;
@@ -138,7 +189,7 @@
// Make a copy because closing the remote inputs will modify mOpen.
ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size());
for (int i = mOpen.size() - 1; i >= 0; i--) {
- NotificationData.Entry item = mOpen.get(i).get();
+ NotificationData.Entry item = mOpen.get(i).first.get();
if (item != null && item.row != null) {
list.add(item);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index 59e4244..f46fc67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.android.keyguard.AlphaOptimizedImageButton;
@@ -41,6 +42,7 @@
public void onFinishInflate() {
super.onFinishInflate();
mIcon = (AlphaOptimizedImageButton) findViewById(R.id.car_nav_button_icon);
+ mIcon.setScaleType(ImageView.ScaleType.CENTER);
mIcon.setClickable(false);
mIcon.setBackgroundColor(android.R.color.transparent);
mIcon.setAlpha(UNSELECTED_ALPHA);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index cf87ddd..8a5a8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -105,7 +105,7 @@
R.dimen.status_bar_connected_device_signal_margin_end));
mConnectedDeviceSignalController = new ConnectedDeviceSignalController(mContext,
- mSignalsView);
+ mSignalsView, mBluetoothController);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
@@ -248,6 +248,11 @@
}
}
+ @Override
+ public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+ // Do nothing, we don't want to display media art in the lock screen for a car.
+ }
+
private int startActivityWithOptions(Intent intent, Bundle options) {
int result = ActivityManager.START_CANCELED;
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index 07856daa9..66030b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -17,11 +17,15 @@
import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
+import com.android.systemui.statusbar.policy.BluetoothController;
+
+import static com.android.systemui.statusbar.phone.PhoneStatusBar.DEBUG;
/**
* Controller that monitors signal strength for a device that is connected via bluetooth.
*/
-public class ConnectedDeviceSignalController extends BroadcastReceiver {
+public class ConnectedDeviceSignalController extends BroadcastReceiver implements
+ BluetoothController.Callback {
private final static String TAG = "DeviceSignalCtlr";
/**
@@ -54,18 +58,21 @@
private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
private final Context mContext;
- private final View mSignalsView;
+ private final BluetoothController mController;
+ private final View mSignalsView;
private final ImageView mNetworkSignalView;
private final float mIconScaleFactor;
private BluetoothHeadsetClient mBluetoothHeadsetClient;
- public ConnectedDeviceSignalController(Context context, View signalsView) {
+ public ConnectedDeviceSignalController(Context context, View signalsView,
+ BluetoothController controller) {
mContext = context;
- mSignalsView = signalsView;
+ mController = controller;
+ mSignalsView = signalsView;
mNetworkSignalView = (ImageView)
mSignalsView.findViewById(R.id.connected_device_network_signal);
@@ -86,22 +93,46 @@
filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
mContext.registerReceiver(this, filter);
+
+ mController.addStateChangedCallback(this);
}
public void stopListening() {
mContext.unregisterReceiver(this);
+ mController.removeStateChangedCallback(this);
+ }
+
+ @Override
+ public void onBluetoothDevicesChanged() {
+ // Nothing to do here because this Controller is not displaying a list of possible
+ // bluetooth devices.
+ }
+
+ @Override
+ public void onBluetoothStateChange(boolean enabled) {
+ if (DEBUG) {
+ Log.d(TAG, "onBluetoothStateChange(). enabled: " + enabled);
+ }
+
+ // Only need to handle the case if bluetooth has been disabled, in which case the
+ // signal indicators are hidden. If bluetooth has been enabled, then this class should
+ // receive updates to the connection state via onReceive().
+ if (!enabled) {
+ mNetworkSignalView.setVisibility(View.GONE);
+ mSignalsView.setVisibility(View.GONE);
+ }
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "onReceive(). action: " + action);
}
if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "Received ACTION_AG_EVENT");
}
@@ -109,13 +140,13 @@
} else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: "
+ oldState + " -> " + newState);
}
BluetoothDevice device =
- (BluetoothDevice)intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
+ (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
updateViewVisibility(device, newState);
}
}
@@ -128,7 +159,7 @@
int networkStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
INVALID_SIGNAL);
if (networkStatus != INVALID_SIGNAL) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_STATUS: " + " " + networkStatus);
}
@@ -140,7 +171,7 @@
int signalStrength = intent.getIntExtra(
BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL);
if (signalStrength != INVALID_SIGNAL) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength);
}
@@ -150,7 +181,7 @@
int roamingStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
INVALID_SIGNAL);
if (roamingStatus != INVALID_SIGNAL) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_ROAMING: " + roamingStatus);
}
}
@@ -169,7 +200,7 @@
private void updateViewVisibility(BluetoothDevice device, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "Device connected");
}
@@ -186,14 +217,14 @@
int signalStrength = featuresBundle.getInt(
BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL);
if (signalStrength != INVALID_SIGNAL) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength);
}
setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]);
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "Device disconnected");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index 95cb672..f6fe176 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,11 +14,11 @@
package com.android.systemui.statusbar.phone;
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.view.View;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
+
import java.util.ArrayList;
/**
@@ -186,18 +186,4 @@
}
}
- /**
- * Interface for button actions.
- */
- public interface ButtonInterface {
- void setImageResource(@DrawableRes int resId);
-
- void setImageDrawable(@Nullable Drawable drawable);
-
- void abortCurrentGesture();
-
- void setLandscape(boolean landscape);
-
- void setCarMode(boolean carMode);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index d5bf499..9e9bdd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -18,11 +18,13 @@
import android.content.Context;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.SparseBooleanArray;
+import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.R;
import java.io.PrintWriter;
@@ -31,9 +33,6 @@
import java.util.regex.Pattern;
public class DozeParameters {
- private static final String TAG = "DozeParameters";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
private static final int MAX_DURATION = 60 * 1000;
private final Context mContext;
@@ -55,10 +54,8 @@
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
pw.print(" getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
- pw.print(" getPulseOnPickup(): "); pw.println(getPulseOnPickup());
pw.print(" getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
pw.print(" getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
- pw.print(" getPulseOnNotifications(): "); pw.println(getPulseOnNotifications());
pw.print(" getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
pw.print(" getPickupSubtypePerformsProxCheck(): ");pw.println(
dumpPickupSubtypePerformsProxCheck());
@@ -106,26 +103,14 @@
return SystemProperties.getBoolean("doze.vibrate.sigmotion", false);
}
- public boolean getPulseOnPickup() {
- return getBoolean("doze.pulse.pickup", R.bool.doze_pulse_on_pick_up);
- }
-
public boolean getVibrateOnPickup() {
return SystemProperties.getBoolean("doze.vibrate.pickup", false);
}
- public String getDoubleTapSensorType() {
- return mContext.getString(R.string.doze_double_tap_sensor_type);
- }
-
public boolean getProxCheckBeforePulse() {
return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
}
- public boolean getPulseOnNotifications() {
- return getBoolean("doze.pulse.notifications", R.bool.doze_pulse_on_notifications);
- }
-
public int getPickupVibrationThreshold() {
return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 82867c6..42d9433 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -30,7 +30,7 @@
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.LatencyTracker;
+import com.android.keyguard.LatencyTracker;
import com.android.systemui.keyguard.KeyguardViewMediator;
/**
@@ -42,8 +42,6 @@
private static final boolean DEBUG_FP_WAKELOCK = KeyguardConstants.DEBUG_FP_WAKELOCK;
private static final long FINGERPRINT_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String FINGERPRINT_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
- private static final String ACTION_FINGERPRINT_WAKE_FAKE =
- "com.android.systemui.ACTION_FINGERPRINT_WAKE_FAKE";
/**
* Mode in which we don't need to wake up the device when we get a fingerprint.
@@ -123,14 +121,6 @@
mScrimController = scrimController;
mPhoneStatusBar = phoneStatusBar;
mUnlockMethodCache = unlockMethodCache;
- if (Build.IS_DEBUGGABLE) {
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- fakeWakeAndUnlock();
- }
- }, new IntentFilter(ACTION_FINGERPRINT_WAKE_FAKE));
- }
}
public void setStatusBarKeyguardViewManager(
@@ -159,11 +149,6 @@
}
}
- public void fakeWakeAndUnlock() {
- onFingerprintAcquired();
- onFingerprintAuthenticated(KeyguardUpdateMonitor.getCurrentUser());
- }
-
@Override
public void onFingerprintAcquired() {
Trace.beginSection("FingerprintUnlockController#onFingerprintAcquired");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a391eb..f9b7bb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -61,6 +61,11 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.IntentButtonProvider;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSContainer.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -86,6 +91,11 @@
public static final String EXTRA_CAMERA_LAUNCH_SOURCE
= "com.android.systemui.camera_launch_source";
+ private static final String LEFT_BUTTON_PLUGIN
+ = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
+ private static final String RIGHT_BUTTON_PLUGIN
+ = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
+
private static final Intent SECURE_CAMERA_INTENT =
new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
@@ -95,7 +105,7 @@
private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
- private KeyguardAffordanceView mCameraImageView;
+ private KeyguardAffordanceView mRightAffordanceView;
private KeyguardAffordanceView mLeftAffordanceView;
private LockIcon mLockIcon;
private TextView mIndicationText;
@@ -112,6 +122,7 @@
private KeyguardIndicationController mIndicationController;
private AccessibilityController mAccessibilityController;
private PhoneStatusBar mPhoneStatusBar;
+ private KeyguardAffordanceHelper mAffordanceHelper;
private boolean mUserSetupComplete;
private boolean mPrewarmBound;
@@ -132,6 +143,9 @@
private boolean mLeftIsVoiceAssist;
private AssistManager mAssistManager;
+ private IntentButton mRightButton = new DefaultRightButton();
+ private IntentButton mLeftButton = new DefaultLeftButton();
+
public KeyguardBottomAreaView(Context context) {
this(context, null);
}
@@ -156,7 +170,7 @@
String label = null;
if (host == mLockIcon) {
label = getResources().getString(R.string.unlock_label);
- } else if (host == mCameraImageView) {
+ } else if (host == mRightAffordanceView) {
label = getResources().getString(R.string.camera_label);
} else if (host == mLeftAffordanceView) {
if (mLeftIsVoiceAssist) {
@@ -175,7 +189,7 @@
mPhoneStatusBar.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
return true;
- } else if (host == mCameraImageView) {
+ } else if (host == mRightAffordanceView) {
launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
return true;
} else if (host == mLeftAffordanceView) {
@@ -192,7 +206,7 @@
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
- mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
+ mRightAffordanceView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
mLeftAffordanceView = (KeyguardAffordanceView) findViewById(R.id.left_button);
mLockIcon = (LockIcon) findViewById(R.id.lock_icon);
mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
@@ -207,15 +221,31 @@
inflateCameraPreview();
mLockIcon.setOnClickListener(this);
mLockIcon.setOnLongClickListener(this);
- mCameraImageView.setOnClickListener(this);
+ mRightAffordanceView.setOnClickListener(this);
mLeftAffordanceView.setOnClickListener(this);
initAccessibility();
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ PluginManager.getInstance(getContext()).addPluginListener(RIGHT_BUTTON_PLUGIN,
+ mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN,
+ mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ PluginManager.getInstance(getContext()).removePluginListener(mRightListener);
+ PluginManager.getInstance(getContext()).removePluginListener(mLeftListener);
+ }
+
private void initAccessibility() {
mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
- mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
+ mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
}
@Override
@@ -234,11 +264,11 @@
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
- ViewGroup.LayoutParams lp = mCameraImageView.getLayoutParams();
+ ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
- mCameraImageView.setLayoutParams(lp);
- mCameraImageView.setImageDrawable(mContext.getDrawable(R.drawable.ic_camera_alt_24dp));
+ mRightAffordanceView.setLayoutParams(lp);
+ updateRightAffordanceIcon();
lp = mLockIcon.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
@@ -253,6 +283,13 @@
updateLeftAffordanceIcon();
}
+ private void updateRightAffordanceIcon() {
+ IconState state = mRightButton.getIcon();
+ mRightAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
+ mRightAffordanceView.setImageDrawable(state.drawable);
+ mRightAffordanceView.setContentDescription(state.contentDescription);
+ }
+
public void setActivityStarter(ActivityStarter activityStarter) {
mActivityStarter = activityStarter;
}
@@ -272,6 +309,10 @@
updateCameraVisibility(); // in case onFinishInflate() was called too early
}
+ public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
+ mAffordanceHelper = affordanceHelper;
+ }
+
public void setUserSetupComplete(boolean userSetupComplete) {
mUserSetupComplete = userSetupComplete;
updateCameraVisibility();
@@ -279,11 +320,7 @@
}
private Intent getCameraIntent() {
- KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser());
- boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
- return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ return mRightButton.getIntent();
}
/**
@@ -296,33 +333,19 @@
}
private void updateCameraVisibility() {
- if (mCameraImageView == null) {
+ if (mRightAffordanceView == null) {
// Things are not set up yet; reply hazy, ask again later
return;
}
- ResolveInfo resolved = resolveCameraIntent();
- boolean visible = !isCameraDisabledByDpm() && resolved != null
- && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
- && mUserSetupComplete;
- mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mRightAffordanceView.setVisibility(mRightButton.getIcon().isVisible
+ ? View.VISIBLE : View.GONE);
}
private void updateLeftAffordanceIcon() {
- mLeftIsVoiceAssist = canLaunchVoiceAssist();
- int drawableId;
- int contentDescription;
- boolean visible = mUserSetupComplete;
- if (mLeftIsVoiceAssist) {
- drawableId = R.drawable.ic_mic_26dp;
- contentDescription = R.string.accessibility_voice_assist_button;
- } else {
- visible &= isPhoneVisible();
- drawableId = R.drawable.ic_phone_24dp;
- contentDescription = R.string.accessibility_phone_button;
- }
- mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE);
- mLeftAffordanceView.setImageDrawable(mContext.getDrawable(drawableId));
- mLeftAffordanceView.setContentDescription(mContext.getString(contentDescription));
+ IconState state = mLeftButton.getIcon();
+ mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
+ mLeftAffordanceView.setImageDrawable(state.drawable);
+ mLeftAffordanceView.setContentDescription(state.contentDescription);
}
public boolean isLeftVoiceAssist() {
@@ -363,16 +386,16 @@
@Override
public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
- mCameraImageView.setClickable(touchExplorationEnabled);
+ mRightAffordanceView.setClickable(touchExplorationEnabled);
mLeftAffordanceView.setClickable(touchExplorationEnabled);
- mCameraImageView.setFocusable(accessibilityEnabled);
+ mRightAffordanceView.setFocusable(accessibilityEnabled);
mLeftAffordanceView.setFocusable(accessibilityEnabled);
mLockIcon.update();
}
@Override
public void onClick(View v) {
- if (v == mCameraImageView) {
+ if (v == mRightAffordanceView) {
launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
} else if (v == mLeftAffordanceView) {
launchLeftAffordance();
@@ -541,7 +564,7 @@
}
});
} else {
- mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
+ mActivityStarter.startActivity(mLeftButton.getIntent(), false /* dismissShade */);
}
}
@@ -560,7 +583,7 @@
}
public KeyguardAffordanceView getRightView() {
- return mCameraImageView;
+ return mRightAffordanceView;
}
public View getLeftPreview() {
@@ -602,6 +625,9 @@
mPreviewContainer.addView(mCameraPreview);
mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
}
+ if (mAffordanceHelper != null) {
+ mAffordanceHelper.updatePreviews();
+ }
}
private void updateLeftPreview() {
@@ -613,12 +639,15 @@
mLeftPreview = mPreviewInflater.inflatePreviewFromService(
mAssistManager.getVoiceInteractorComponentName());
} else {
- mLeftPreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
+ mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
}
if (mLeftPreview != null) {
mPreviewContainer.addView(mLeftPreview);
mLeftPreview.setVisibility(View.INVISIBLE);
}
+ if (mAffordanceHelper != null) {
+ mAffordanceHelper.updatePreviews();
+ }
}
public void startFinishDozeAnimation() {
@@ -629,8 +658,8 @@
}
startFinishDozeAnimationElement(mLockIcon, delay);
delay += DOZE_ANIMATION_STAGGER_DELAY;
- if (mCameraImageView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mCameraImageView, delay);
+ if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mRightAffordanceView, delay);
}
mIndicationText.setAlpha(0f);
mIndicationText.animate()
@@ -664,46 +693,53 @@
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
- @Override
- public void onUserSwitchComplete(int userId) {
- updateCameraVisibility();
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ updateCameraVisibility();
+ }
+
+ @Override
+ public void onStartedWakingUp() {
+ mLockIcon.setDeviceInteractive(true);
+ }
+
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ mLockIcon.setDeviceInteractive(false);
+ }
+
+ @Override
+ public void onScreenTurnedOn() {
+ mLockIcon.setScreenOn(true);
+ }
+
+ @Override
+ public void onScreenTurnedOff() {
+ mLockIcon.setScreenOn(false);
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mLockIcon.update();
+ }
+
+ @Override
+ public void onFingerprintRunningStateChanged(boolean running) {
+ mLockIcon.update();
+ }
+
+ @Override
+ public void onStrongAuthStateChanged(int userId) {
+ mLockIcon.update();
}
- @Override
- public void onStartedWakingUp() {
- mLockIcon.setDeviceInteractive(true);
- }
-
- @Override
- public void onFinishedGoingToSleep(int why) {
- mLockIcon.setDeviceInteractive(false);
- }
-
- @Override
- public void onScreenTurnedOn() {
- mLockIcon.setScreenOn(true);
- }
-
- @Override
- public void onScreenTurnedOff() {
- mLockIcon.setScreenOn(false);
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mLockIcon.update();
- }
-
- @Override
- public void onFingerprintRunningStateChanged(boolean running) {
- mLockIcon.update();
- }
-
- @Override
- public void onStrongAuthStateChanged(int userId) {
- mLockIcon.update();
- }
- };
+ @Override
+ public void onUserUnlocked() {
+ inflateCameraPreview();
+ updateCameraVisibility();
+ updateLeftAffordance();
+ }
+ };
public void setKeyguardIndicationController(
KeyguardIndicationController keyguardIndicationController) {
@@ -724,4 +760,96 @@
updateLeftAffordance();
inflateCameraPreview();
}
+
+ private void setRightButton(IntentButton button) {
+ mRightButton = button;
+ updateRightAffordanceIcon();
+ updateCameraVisibility();
+ inflateCameraPreview();
+ }
+
+ private void setLeftButton(IntentButton button) {
+ mLeftButton = button;
+ mLeftIsVoiceAssist = false;
+ updateLeftAffordance();
+ }
+
+ private final PluginListener<IntentButtonProvider> mRightListener =
+ new PluginListener<IntentButtonProvider>() {
+ @Override
+ public void onPluginConnected(IntentButtonProvider plugin) {
+ setRightButton(plugin.getIntentButton());
+ }
+
+ @Override
+ public void onPluginDisconnected(IntentButtonProvider plugin) {
+ setRightButton(new DefaultRightButton());
+ }
+ };
+
+ private final PluginListener<IntentButtonProvider> mLeftListener =
+ new PluginListener<IntentButtonProvider>() {
+ @Override
+ public void onPluginConnected(IntentButtonProvider plugin) {
+ setLeftButton(plugin.getIntentButton());
+ }
+
+ @Override
+ public void onPluginDisconnected(IntentButtonProvider plugin) {
+ setLeftButton(new DefaultLeftButton());
+ }
+ };
+
+ private class DefaultLeftButton implements IntentButton {
+
+ private IconState mIconState = new IconState();
+
+ @Override
+ public IconState getIcon() {
+ mLeftIsVoiceAssist = canLaunchVoiceAssist();
+ if (mLeftIsVoiceAssist) {
+ mIconState.isVisible = mUserSetupComplete;
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
+ mIconState.contentDescription = mContext.getString(
+ R.string.accessibility_voice_assist_button);
+ } else {
+ mIconState.isVisible = mUserSetupComplete && isPhoneVisible();
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
+ mIconState.contentDescription = mContext.getString(
+ R.string.accessibility_phone_button);
+ }
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return PHONE_INTENT;
+ }
+ }
+
+ private class DefaultRightButton implements IntentButton {
+
+ private IconState mIconState = new IconState();
+
+ @Override
+ public IconState getIcon() {
+ ResolveInfo resolved = resolveCameraIntent();
+ mIconState.isVisible = !isCameraDisabledByDpm() && resolved != null
+ && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
+ && mUserSetupComplete;
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
+ mIconState.contentDescription =
+ mContext.getString(R.string.accessibility_camera_button);
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser());
+ boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 706abdc..34b8371 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -96,9 +96,10 @@
}
final int activeUserId = ActivityManager.getCurrentUser();
- final boolean allowDismissKeyguard =
- !UserManager.isSplitSystemUser()
- && activeUserId == keyguardUserId;
+ final boolean isSystemUser =
+ UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
+ final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
+
// If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern) is
// set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) {
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/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 06c8b68..c420927 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -28,12 +28,19 @@
import android.widget.Space;
import com.android.systemui.R;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
-public class NavigationBarInflaterView extends FrameLayout implements TunerService.Tunable {
+public class NavigationBarInflaterView extends FrameLayout
+ implements Tunable, PluginListener<NavBarButtonProvider> {
private static final String TAG = "NavBarInflater";
@@ -57,6 +64,8 @@
public static final String KEY_IMAGE_DELIM = ":";
public static final String KEY_CODE_END = ")";
+ private final List<NavBarButtonProvider> mPlugins = new ArrayList<>();
+
protected LayoutInflater mLayoutInflater;
protected LayoutInflater mLandscapeInflater;
private int mDensity;
@@ -129,6 +138,8 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
+ NavBarButtonProvider.VERSION, true /* Allow multiple */);
}
@Override
@@ -240,8 +251,36 @@
int indexInParent) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
float size = extractSize(buttonSpec);
- String button = extractButton(buttonSpec);
+ View v = createView(buttonSpec, parent, inflater, landscape);
+ if (v == null) return null;
+
+ if (size != 0) {
+ ViewGroup.LayoutParams params = v.getLayoutParams();
+ params.width = (int) (params.width * size);
+ }
+ parent.addView(v);
+ addToDispatchers(v, landscape);
+ View lastView = landscape ? mLastRot90 : mLastRot0;
+ if (lastView != null) {
+ v.setAccessibilityTraversalAfter(lastView.getId());
+ }
+ if (landscape) {
+ mLastRot90 = v;
+ } else {
+ mLastRot0 = v;
+ }
+ return v;
+ }
+
+ private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater,
+ boolean landscape) {
View v = null;
+ String button = extractButton(buttonSpec);
+ // Let plugins go first so they can override a standard view if they want.
+ for (NavBarButtonProvider provider : mPlugins) {
+ v = provider.createView(buttonSpec, parent);
+ if (v != null) return v;
+ }
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
if (landscape && isSw600Dp()) {
@@ -271,24 +310,6 @@
if (uri != null) {
((KeyButtonView) v).loadAsync(uri);
}
- } else {
- return null;
- }
-
- if (size != 0) {
- ViewGroup.LayoutParams params = v.getLayoutParams();
- params.width = (int) (params.width * size);
- }
- parent.addView(v);
- addToDispatchers(v, landscape);
- View lastView = landscape ? mLastRot90 : mLastRot0;
- if (lastView != null) {
- v.setAccessibilityTraversalAfter(lastView.getId());
- }
- if (landscape) {
- mLastRot90 = v;
- } else {
- mLastRot0 = v;
}
return v;
}
@@ -374,4 +395,18 @@
((ViewGroup) group.getChildAt(i)).removeAllViews();
}
}
+
+ @Override
+ public void onPluginConnected(NavBarButtonProvider plugin) {
+ mPlugins.add(plugin);
+ clearViews();
+ inflateLayout(mCurrentLayout);
+ }
+
+ @Override
+ public void onPluginDisconnected(NavBarButtonProvider plugin) {
+ mPlugins.remove(plugin);
+ clearViews();
+ inflateLayout(mCurrentLayout);
+ }
}
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 9e5b881..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,22 +44,28 @@
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";
// slippery nav bar when everything is disabled, e.g. during setup
final static boolean SLIPPERY_WHEN_DISABLED = true;
+ final static boolean ALTERNATE_CAR_MODE_UI = false;
+
final Display mDisplay;
View mCurrentView = null;
View[] mRotatedViews = new View[4];
@@ -81,7 +87,7 @@
private Drawable mImeIcon;
private Drawable mMenuIcon;
- private NavigationBarGestureHelper mGestureHelper;
+ private GestureHelper mGestureHelper;
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
@@ -95,13 +101,16 @@
private OnVerticalChangedListener mOnVerticalChangedListener;
private boolean mLayoutTransitionsEnabled = true;
private boolean mWakeAndUnlocking;
- private boolean mCarMode = false;
+ private boolean mUseCarModeUi = false;
+ private boolean mInCarMode = false;
private boolean mDockedStackExists;
private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>();
private Configuration mConfiguration;
private NavigationBarInflaterView mNavigationInflaterView;
+ private RecentsComponent mRecentsComponent;
+ private Divider mDivider;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -209,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) {
@@ -291,7 +305,9 @@
mMenuIcon = ctx.getDrawable(R.drawable.ic_sysbar_menu);
mImeIcon = ctx.getDrawable(R.drawable.ic_ime_switcher_default);
- updateCarModeIcons(ctx);
+ if (ALTERNATE_CAR_MODE_UI) {
+ updateCarModeIcons(ctx);
+ }
}
}
@@ -342,14 +358,14 @@
// carmode, respectively. Recents are not available in CarMode in nav bar so change
// to recent icon is not required.
Drawable backIcon = (backAlt)
- ? getBackIconWithAlt(mCarMode, mVertical)
- : getBackIcon(mCarMode, mVertical);
+ ? getBackIconWithAlt(mUseCarModeUi, mVertical)
+ : getBackIcon(mUseCarModeUi, mVertical);
getBackButton().setImageDrawable(backIcon);
updateRecentsIcon();
- if (mCarMode) {
+ if (mUseCarModeUi) {
getHomeButton().setImageDrawable(mHomeCarModeIcon);
} else {
getHomeButton().setImageDrawable(mHomeDefaultIcon);
@@ -377,9 +393,9 @@
final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
- // Disable recents always in car mode.
- boolean disableRecent = (
- mCarMode || (disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
+ // Always disable recents when alternate car mode UI is active.
+ boolean disableRecent = mUseCarModeUi
+ || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
&& ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0);
@@ -627,14 +643,19 @@
boolean uiCarModeChanged = false;
if (newConfig != null) {
int uiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK;
- if (mCarMode && uiMode != Configuration.UI_MODE_TYPE_CAR) {
- mCarMode = false;
- uiCarModeChanged = true;
- getHomeButton().setCarMode(mCarMode);
- } else if (uiMode == Configuration.UI_MODE_TYPE_CAR) {
- mCarMode = true;
- uiCarModeChanged = true;
- getHomeButton().setCarMode(mCarMode);
+ final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
+
+ if (isCarMode != mInCarMode) {
+ mInCarMode = isCarMode;
+ getHomeButton().setCarMode(isCarMode);
+
+ if (ALTERNATE_CAR_MODE_UI) {
+ mUseCarModeUi = isCarMode;
+ uiCarModeChanged = true;
+ } else {
+ // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
+ mUseCarModeUi = false;
+ }
}
}
return uiCarModeChanged;
@@ -690,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/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 5d1af2f..7b35cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -233,6 +233,7 @@
mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
+ mKeyguardBottomArea.setAffordanceHelper(mAfforanceHelper);
mLastOrientation = getResources().getConfiguration().orientation;
mQsAutoReinflateContainer =
@@ -328,7 +329,7 @@
} else if (!mQsExpanded) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
}
- updateStackHeight(getExpandedHeight());
+ updateExpandedHeight(getExpandedHeight());
updateHeader();
// If we are running a size change animation, the animation takes care of the height of
@@ -376,10 +377,7 @@
boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
if (mStatusBarState != StatusBarState.KEYGUARD) {
- int bottom = mQsContainer.getHeader().getHeight();
- stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
- ? bottom + mQsPeekHeight
- : mKeyguardStatusBar.getHeight();
+ stackScrollerPadding = mQsContainer.getHeader().getHeight() + mQsPeekHeight;
mTopPaddingAdjustment = 0;
} else {
mClockPositionAlgorithm.setup(
@@ -1004,8 +1002,8 @@
mKeyguardShowing = keyguardShowing;
mQsContainer.setKeyguardShowing(mKeyguardShowing);
- if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
- && statusBarState == StatusBarState.SHADE_LOCKED)) {
+ if (oldState == StatusBarState.KEYGUARD
+ && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
animateKeyguardStatusBarOut();
long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
? 0 : mStatusBar.calculateGoingToFullShadeDelay();
@@ -1019,7 +1017,7 @@
mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
if (keyguardShowing && oldState != mStatusBarState) {
mKeyguardBottomArea.onKeyguardShowingChanged();
- mAfforanceHelper.updatePreviews();
+ mQsContainer.hideImmediately();
}
}
if (keyguardShowing) {
@@ -1166,6 +1164,7 @@
private void updateQsState() {
mQsContainer.setExpanded(mQsExpanded);
+ mNotificationStackScroller.setQsExpanded(mQsExpanded);
mNotificationStackScroller.setScrollingEnabled(
mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
@@ -1314,9 +1313,12 @@
}
return;
}
- boolean belowFalsingThreshold = isFalseTouch();
- if (belowFalsingThreshold) {
+
+ // If we move in the opposite direction, reset velocity and use a different duration.
+ boolean oppositeDirection = false;
+ if (vel > 0 && !expand || vel < 0 && expand) {
vel = 0;
+ oppositeDirection = true;
}
ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
if (isClick) {
@@ -1325,7 +1327,7 @@
} else {
mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
}
- if (belowFalsingThreshold) {
+ if (oppositeDirection) {
animator.setDuration(350);
}
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -1427,7 +1429,7 @@
setQsExpansion(mQsMinExpansionHeight
+ t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
}
- updateStackHeight(expandedHeight);
+ updateExpandedHeight(expandedHeight);
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
@@ -1487,7 +1489,7 @@
maxQsHeight, mStatusBarState == StatusBarState.KEYGUARD
? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
: 0)
- + notificationHeight;
+ + notificationHeight + mNotificationStackScroller.getTopPaddingOverflow();
if (totalHeight > mNotificationStackScroller.getHeight()) {
float fullyCollapsedHeight = maxQsHeight
+ mNotificationStackScroller.getLayoutMinHeight();
@@ -1730,6 +1732,14 @@
if (view == null && mQsExpanded) {
return;
}
+ ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+ ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) firstChildNotGone
+ : null;
+ if (firstRow != null
+ && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
+ requestScrollerTopPaddingUpdate(false);
+ }
requestPanelHeightUpdate();
}
@@ -2249,8 +2259,8 @@
mQsAutoReinflateContainer.setTranslationX(translation);
}
- protected void updateStackHeight(float stackHeight) {
- mNotificationStackScroller.setStackHeight(stackHeight);
+ protected void updateExpandedHeight(float expandedHeight) {
+ mNotificationStackScroller.setExpandedHeight(expandedHeight);
updateKeyguardBottomAreaAlpha();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index c6aec73..3de03b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
@@ -37,7 +36,7 @@
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
-import com.android.systemui.LatencyTracker;
+import com.android.keyguard.LatencyTracker;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeLog;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 40303c4..28e5f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -37,6 +37,7 @@
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.IActivityManager;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
@@ -132,7 +133,7 @@
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
-import com.android.systemui.LatencyTracker;
+import com.android.keyguard.LatencyTracker;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
@@ -313,7 +314,7 @@
PhoneStatusBarPolicy mIconPolicy;
// These are no longer handled by the policy, because we need custom strategies for them
- BluetoothControllerImpl mBluetoothController;
+ protected BluetoothControllerImpl mBluetoothController;
SecurityControllerImpl mSecurityController;
protected BatteryController mBatteryController;
LocationControllerImpl mLocationController;
@@ -1733,7 +1734,7 @@
protected void performRemoveNotification(StatusBarNotification n, boolean removeView) {
Entry entry = mNotificationData.get(n.getKey());
if (mRemoteInputController.isRemoteInputActive(entry)) {
- mRemoteInputController.removeRemoteInput(entry);
+ mRemoteInputController.removeRemoteInput(entry, null);
}
super.performRemoveNotification(n, removeView);
}
@@ -1765,18 +1766,21 @@
for (int i=0; i<N; i++) {
Entry ent = activeNotifications.get(i);
int vis = ent.notification.getNotification().visibility;
+ int userId = ent.notification.getUserId();
// Display public version of the notification if we need to redact.
- final boolean hideSensitive =
- !userAllowsPrivateNotificationsInPublic(ent.notification.getUserId());
+ boolean deviceSensitive = (isLockscreenPublicMode(mCurrentUserId)
+ && !userAllowsPrivateNotificationsInPublic(mCurrentUserId));
+ boolean userSensitive = deviceSensitive || (isLockscreenPublicMode(userId)
+ && !userAllowsPrivateNotificationsInPublic(userId));
boolean sensitiveNote = vis == Notification.VISIBILITY_PRIVATE;
boolean sensitivePackage = packageHasVisibilityOverride(ent.notification.getKey());
- boolean sensitive = (sensitiveNote && hideSensitive) || sensitivePackage;
- boolean showingPublic = sensitive && isLockscreenPublicMode();
+ boolean sensitive = (sensitiveNote && userSensitive) || sensitivePackage;
+ boolean showingPublic = sensitive && isLockscreenPublicMode(userId);
if (showingPublic) {
updatePublicContentView(ent, ent.notification);
}
- ent.row.setSensitive(sensitive, hideSensitive);
+ ent.row.setSensitive(sensitive, deviceSensitive);
if (ent.autoRedacted && ent.legacy) {
// TODO: Also fade this? Or, maybe easier (and better), provide a dark redacted form
// for legacy auto redacted notifications.
@@ -2681,7 +2685,7 @@
private void removeRemoteInputEntriesKeptUntilCollapsed() {
for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
- mRemoteInputController.removeRemoteInput(entry);
+ mRemoteInputController.removeRemoteInput(entry, null);
removeNotification(entry.key, mLatestRankingMap);
}
mRemoteInputEntriesToRemoveOnCollapse.clear();
@@ -2745,7 +2749,7 @@
public void handleSystemNavigationKey(int key) {
if (SPEW) Log.d(TAG, "handleSystemNavigationKey: " + key);
if (!panelsEnabled() || !mKeyguardMonitor.isDeviceInteractive()
- || mKeyguardMonitor.isShowing()) {
+ || mKeyguardMonitor.isShowing() && !mKeyguardMonitor.isOccluded()) {
return;
}
@@ -2980,6 +2984,10 @@
return mGestureRec;
}
+ public FingerprintUnlockController getFingerprintUnlockController() {
+ return mFingerprintUnlockController;
+ }
+
private void setNavigationIconHints(int hints) {
if (hints == mNavigationIconHints) return;
@@ -3427,6 +3435,9 @@
if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
}
+ if (mFlashlightController != null) {
+ mFlashlightController.dump(fd, pw, args);
+ }
FalsingManager.getInstance(mContext).dump(pw);
FalsingLog.dump(pw);
@@ -4258,7 +4269,7 @@
}
updateKeyguardState(staying, false /* fromShadeLocked */);
- if (viewToClick != null) {
+ if (viewToClick != null && viewToClick.isAttachedToWindow()) {
viewToClick.callOnClick();
}
@@ -4333,17 +4344,23 @@
}
private void updatePublicMode() {
- boolean isPublic = false;
- if (mStatusBarKeyguardViewManager.isShowing()) {
- for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
- UserInfo userInfo = mCurrentProfiles.valueAt(i);
- if (mStatusBarKeyguardViewManager.isSecure(userInfo.id)) {
- isPublic = true;
- break;
+ final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
+ final boolean devicePublic = showingKeyguard
+ && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId);
+
+ // Look for public mode users. Users are considered public in either case of:
+ // - device keyguard is shown in secure mode;
+ // - profile is locked with a work challenge.
+ for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
+ final int userId = mCurrentProfiles.valueAt(i).id;
+ boolean isProfilePublic = devicePublic;
+ if (!devicePublic && userId != mCurrentUserId) {
+ if (mStatusBarKeyguardViewManager.isSecure(userId)) {
+ isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
}
}
+ setLockscreenPublicMode(isProfilePublic, userId);
}
- setLockscreenPublicMode(isPublic);
}
protected void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
@@ -4378,7 +4395,8 @@
checkBarModes();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
- mStatusBarKeyguardViewManager.isSecure());
+ mStatusBarKeyguardViewManager.isSecure(),
+ mStatusBarKeyguardViewManager.isOccluded());
Trace.endSection();
}
@@ -4400,7 +4418,8 @@
public void updateStackScrollerState(boolean goingToFullShade, boolean fromShadeLocked) {
if (mStackScroller == null) return;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
- mStackScroller.setHideSensitive(isLockscreenPublicMode(), goingToFullShade);
+ boolean publicMode = isAnyProfilePublicMode();
+ mStackScroller.setHideSensitive(publicMode, goingToFullShade);
mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */);
mStackScroller.setExpandingEnabled(!onKeyguard);
ActivatableNotificationView activatedChild = mStackScroller.getActivatedChild();
@@ -4456,6 +4475,9 @@
animateCollapsePanels();
return true;
}
+ if (mKeyguardUserSwitcher.hideIfNotSimple(true)) {
+ return true;
+ }
return false;
}
@@ -4650,6 +4672,7 @@
* @param expandView The view to expand after going to the shade.
*/
public void goToLockedShade(View expandView) {
+ int userId = mCurrentUserId;
ExpandableNotificationRow row = null;
if (expandView instanceof ExpandableNotificationRow) {
row = (ExpandableNotificationRow) expandView;
@@ -4657,10 +4680,13 @@
// Indicate that the group expansion is changing at this time -- this way the group
// and children backgrounds / divider animations will look correct.
row.setGroupExpansionChanging(true);
+ if (row.getStatusBarNotification() != null) {
+ userId = row.getStatusBarNotification().getUserId();
+ }
}
boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId)
|| !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer();
- if (isLockscreenPublicMode() && fullShadeNeedsBouncer) {
+ if (isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
mLeaveOpenOnKeyguardHide = true;
showBouncer();
mDraggedDownRow = row;
@@ -4705,10 +4731,20 @@
mPendingWorkRemoteInputView = clicked;
}
+ private boolean isAnyProfilePublicMode() {
+ for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
+ if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
- protected void onWorkChallengeUnlocked() {
- if (mPendingWorkRemoteInputView != null) {
- final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+ protected void onWorkChallengeChanged() {
+ updatePublicMode();
+ updateNotifications();
+ if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
// Expand notification panel and the notification row, then click on remote input view
final Runnable clickPendingViewRunnable = new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index b0b86be..a8b0122 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -350,9 +350,7 @@
}
} else if (v == mAlarmStatus && mNextAlarm != null) {
PendingIntent showIntent = mNextAlarm.getShowIntent();
- if (showIntent != null && showIntent.isActivity()) {
- mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */);
- }
+ mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 01609e4..77c60fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -31,7 +31,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
-import com.android.systemui.LatencyTracker;
+import com.android.keyguard.LatencyTracker;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.RemoteInputController;
@@ -264,10 +264,12 @@
}
}
mOccluded = occluded;
- mPhoneStatusBar.updateMediaMetaData(false, animate && !occluded);
+ if (mShowing) {
+ mPhoneStatusBar.updateMediaMetaData(false, animate && !occluded);
+ }
mStatusBarWindowManager.setKeyguardOccluded(occluded);
reset();
- if (animate && !occluded) {
+ if (animate && !occluded && mShowing) {
mPhoneStatusBar.animateKeyguardUnoccluding();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index b9c7a4b..6726c92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -53,6 +53,7 @@
protected boolean mCharged;
protected boolean mPowerSave;
private boolean mTestmode = false;
+ private boolean mHasReceivedBattery = false;
public BatteryControllerImpl(Context context) {
mContext = context;
@@ -92,6 +93,7 @@
synchronized (mChangeCallbacks) {
mChangeCallbacks.add(cb);
}
+ if (!mHasReceivedBattery) return;
cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
cb.onPowerSaveChanged(mPowerSave);
}
@@ -108,6 +110,7 @@
final String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
+ mHasReceivedBattery = true;
mLevel = (int)(100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 91b21ed..4e9fc76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -27,6 +27,8 @@
import android.text.TextUtils;
import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -80,6 +82,7 @@
public void setFlashlight(boolean enabled) {
boolean pendingError = false;
synchronized (this) {
+ if (mCameraId == null) return;
if (mFlashlightEnabled != enabled) {
mFlashlightEnabled = enabled;
try {
@@ -235,6 +238,17 @@
}
};
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("FlashlightController state:");
+
+ pw.print(" mCameraId=");
+ pw.println(mCameraId);
+ pw.print(" mFlashlightEnabled=");
+ pw.println(mFlashlightEnabled);
+ pw.print(" mTorchAvailable=");
+ pw.println(mTorchAvailable);
+ }
+
public interface FlashlightListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index e6066aa..bcc5a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -44,12 +44,12 @@
import android.widget.ImageView;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ButtonDispatcher;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
-public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonInterface {
+public class KeyButtonView extends ImageView implements ButtonInterface {
private int mContentDescriptionRes;
private long mDownTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index c175180..44816f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -38,6 +38,7 @@
private int mCurrentUser;
private boolean mShowing;
private boolean mSecure;
+ private boolean mOccluded;
private boolean mCanSkipBouncer;
private boolean mListening;
@@ -81,6 +82,10 @@
return mSecure;
}
+ public boolean isOccluded() {
+ return mOccluded;
+ }
+
public boolean canSkipBouncer() {
return mCanSkipBouncer;
}
@@ -99,10 +104,11 @@
}
}
- public void notifyKeyguardState(boolean showing, boolean secure) {
- if (mShowing == showing && mSecure == secure) return;
+ public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
+ if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
mShowing = showing;
mSecure = secure;
+ mOccluded = occluded;
notifyKeyguardChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 21f3f5e..1cf4050 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -128,7 +128,7 @@
}
}
- private void hide(boolean animate) {
+ private boolean hide(boolean animate) {
if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
cancelAnimations();
if (animate) {
@@ -137,7 +137,9 @@
mUserSwitcherContainer.setVisibility(View.GONE);
}
mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
+ return true;
}
+ return false;
}
private void cancelAnimations() {
@@ -223,10 +225,11 @@
}
}
- public void hideIfNotSimple(boolean animate) {
+ public boolean hideIfNotSimple(boolean animate) {
if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
- hide(animate);
+ return hide(animate);
}
+ return false;
}
boolean isAnimating() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index ab2a8bc..7b1f707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -69,6 +69,8 @@
// A marker object that let's us easily find views of this class.
public static final Object VIEW_TAG = new Object();
+ public final Object mToken = new Object();
+
private RemoteEditText mEditText;
private ImageButton mSendButton;
private ProgressBar mProgressBar;
@@ -140,8 +142,8 @@
mSendButton.setVisibility(INVISIBLE);
mProgressBar.setVisibility(VISIBLE);
mEntry.remoteInputText = mEditText.getText();
- mController.addSpinning(mEntry.key);
- mController.removeRemoteInput(mEntry);
+ mController.addSpinning(mEntry.key, mToken);
+ mController.removeRemoteInput(mEntry, mToken);
mEditText.mShowImeOnInputConnection = false;
mController.remoteInputSent(mEntry);
@@ -193,7 +195,7 @@
}
private void onDefocus(boolean animate) {
- mController.removeRemoteInput(mEntry);
+ mController.removeRemoteInput(mEntry, mToken);
mEntry.remoteInputText = mEditText.getText();
// During removal, we get reattached and lose focus. Not hiding in that
@@ -232,11 +234,11 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mEntry.row.isChangingPosition()) {
+ if (mEntry.row.isChangingPosition() || isTemporarilyDetached()) {
return;
}
- mController.removeRemoteInput(mEntry);
- mController.removeSpinning(mEntry.key);
+ mController.removeRemoteInput(mEntry, mToken);
+ mController.removeSpinning(mEntry.key, mToken);
}
public void setPendingIntent(PendingIntent pendingIntent) {
@@ -265,7 +267,7 @@
mEntry.notification.getPackageName());
setVisibility(VISIBLE);
- mController.addRemoteInput(mEntry);
+ mController.addRemoteInput(mEntry, mToken);
mEditText.setInnerFocusable(true);
mEditText.mShowImeOnInputConnection = true;
mEditText.setText(mEntry.remoteInputText);
@@ -290,7 +292,7 @@
mEditText.setEnabled(true);
mSendButton.setVisibility(VISIBLE);
mProgressBar.setVisibility(INVISIBLE);
- mController.removeSpinning(mEntry.key);
+ mController.removeSpinning(mEntry.key, mToken);
updateSendButton();
onDefocus(false /* animate */);
@@ -432,6 +434,24 @@
mRevealR = r;
}
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+ // won't lose IME focus.
+ detachViewFromParent(mEditText);
+ }
+
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ if (isAttachedToWindow()) {
+ attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+ } else {
+ removeDetachedView(mEditText, false /* animate */);
+ }
+ super.dispatchFinishTemporaryDetach();
+ }
+
/**
* An EditText that changes appearance based on whether it's focusable and becomes
* un-focusable whenever the user navigates away from it or it becomes invisible.
@@ -448,7 +468,15 @@
}
private void defocusIfNeeded(boolean animate) {
- if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()) {
+ if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()
+ || isTemporarilyDetached()) {
+ if (isTemporarilyDetached()) {
+ // We might get reattached but then the other one of HUN / expanded might steal
+ // our focus, so we'll need to save our text here.
+ if (mRemoteInputView != null) {
+ mRemoteInputView.mEntry.remoteInputText = getText();
+ }
+ }
return;
}
if (isFocusable() && isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 50e5b88..81da672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -43,6 +43,7 @@
private boolean mShadeExpanded;
private float mMaxHeadsUpTranslation;
private boolean mDismissAllInProgress;
+ private int mLayoutMinHeight;
public int getScrollY() {
return mScrollY;
@@ -137,10 +138,6 @@
mStackTranslation = stackTranslation;
}
- public int getLayoutHeight() {
- return mLayoutHeight;
- }
-
public void setLayoutHeight(int layoutHeight) {
mLayoutHeight = layoutHeight;
}
@@ -154,7 +151,7 @@
}
public int getInnerHeight() {
- return mLayoutHeight - mTopPadding;
+ return Math.max(mLayoutHeight - mTopPadding, mLayoutMinHeight);
}
public boolean isShadeExpanded() {
@@ -180,4 +177,8 @@
public boolean isDismissAllInProgress() {
return mDismissAllInProgress;
}
+
+ public void setLayoutMinHeight(int layoutMinHeight) {
+ mLayoutMinHeight = layoutMinHeight;
+ }
}
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 90f4100..da58d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -112,11 +112,7 @@
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
- /**
- * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
- * externally from {@link #setStackHeight}
- */
- private float mLastSetStackHeight;
+ private float mExpandedHeight;
private int mOwnScrollY;
private int mMaxLayoutHeight;
@@ -355,6 +351,9 @@
return object.getBackgroundFadeAmount();
}
};
+ private boolean mQsExpanded;
+ private boolean mForwardScrollable;
+ private boolean mBackwardScrollable;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -520,6 +519,7 @@
clampScrollPosition();
requestChildrenUpdate();
updateFirstAndLastBackgroundViews();
+ updateAlgorithmLayoutMinHeight();
}
private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
@@ -561,9 +561,14 @@
private void updateAlgorithmHeightAndPadding() {
mAmbientState.setLayoutHeight(getLayoutHeight());
+ updateAlgorithmLayoutMinHeight();
mAmbientState.setTopPadding(mTopPadding);
}
+ private void updateAlgorithmLayoutMinHeight() {
+ mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() ? getLayoutMinHeight() : 0);
+ }
+
/**
* Updates the children views according to the stack scroll algorithm. Call this whenever
* modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
@@ -594,7 +599,7 @@
if (startingPosition < mOwnScrollY) {
// This child starts off screen, so let's keep it offscreen to keep the others visible
- mOwnScrollY += childHeight;
+ setOwnScrollY(mOwnScrollY + childHeight);
}
}
}
@@ -617,7 +622,7 @@
// Only apply the scroll if we're scrolling the view upwards, or the view is so far up
// that it is not visible anymore.
if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
- mOwnScrollY = targetScroll;
+ setOwnScrollY(targetScroll);
}
}
}
@@ -637,7 +642,7 @@
private void clampScrollPosition() {
int scrollRange = getScrollRange();
if (scrollRange < mOwnScrollY) {
- mOwnScrollY = scrollRange;
+ setOwnScrollY(scrollRange);
}
}
@@ -660,19 +665,19 @@
}
/**
- * Update the height of the stack to a new height.
+ * Update the height of the panel.
*
- * @param height the new height of the stack
+ * @param height the expanded height of the panel
*/
- public void setStackHeight(float height) {
- mLastSetStackHeight = height;
+ public void setExpandedHeight(float height) {
+ mExpandedHeight = height;
setIsExpanded(height > 0.0f);
int stackHeight;
float translationY;
float appearEndPosition = getAppearEndPosition();
float appearStartPosition = getAppearStartPosition();
if (height >= appearEndPosition) {
- translationY = mTopPaddingOverflow;
+ translationY = 0;
stackHeight = (int) height;
} else {
float appearFraction = getAppearFraction(height);
@@ -699,8 +704,12 @@
* Measured relative to the resting position.
*/
private float getExpandTranslationStart() {
- int startPosition = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
- ? 0 : -getFirstChildIntrinsicHeight();
+ int startPosition = 0;
+ if (!mTrackingHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
+ startPosition = - Math.min(getFirstChildIntrinsicHeight(),
+ mMaxLayoutHeight - mIntrinsicPadding - mBottomStackSlowDownHeight
+ - mBottomStackPeekSize);
+ }
return startPosition - mTopPadding;
}
@@ -723,7 +732,7 @@
? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
+ mBottomStackSlowDownHeight
: getLayoutMinHeight();
- return firstItemHeight + mTopPadding + mTopPaddingOverflow;
+ return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
}
/**
@@ -1048,7 +1057,7 @@
@Override
public int getMaxExpandHeight(ExpandableView view) {
int maxContentHeight = view.getMaxContentHeight();
- if (view.isSummaryWithChildren()) {
+ if (view.isSummaryWithChildren() && view.getParent() == this) {
// Faking a measure with the group expanded to simulate how the group would look if
// it was. Doing a calculation here would be highly non-trivial because of the
// algorithm
@@ -1063,8 +1072,11 @@
row.getStatusBarNotification());
mGroupExpandedForMeasure = false;
row.setForceUnlocked(false);
- int height = mCurrentStackScrollState.getViewStateForView(view).height;
- return Math.min(height, maxContentHeight);
+ StackViewState viewState = mCurrentStackScrollState.getViewStateForView(view);
+ if (viewState != null) {
+ // The view could have been removed
+ return Math.min(viewState.height, maxContentHeight);
+ }
}
return maxContentHeight;
}
@@ -1153,6 +1165,10 @@
@Override
public boolean isAntiFalsingNeeded() {
+ return onKeyguard();
+ }
+
+ private boolean onKeyguard() {
return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
}
@@ -1262,7 +1278,7 @@
if (!isScrollingEnabled()) {
return false;
}
- if (isInsideQsContainer(ev)) {
+ if (isInsideQsContainer(ev) && !mIsBeingDragged) {
return false;
}
mForcedScroll = null;
@@ -1431,7 +1447,7 @@
false /* onTop */,
false /* animate */);
}
- mOwnScrollY = range;
+ setOwnScrollY(range);
scrollAmount = 0.0f;
}
return scrollAmount;
@@ -1462,7 +1478,7 @@
setOverScrolledPixels(currentTopPixels - newScrollY,
true /* onTop */,
false /* animate */);
- mOwnScrollY = 0;
+ setOwnScrollY(0);
scrollAmount = 0.0f;
}
return scrollAmount;
@@ -1676,7 +1692,7 @@
}
private void customScrollTo(int y) {
- mOwnScrollY = y;
+ setOwnScrollY(y);
updateChildren();
}
@@ -1687,7 +1703,7 @@
final int oldX = mScrollX;
final int oldY = mOwnScrollY;
mScrollX = scrollX;
- mOwnScrollY = scrollY;
+ setOwnScrollY(scrollY);
if (clampedY) {
springBack();
} else {
@@ -1717,12 +1733,12 @@
if (overScrolledTop) {
onTop = true;
newAmount = -mOwnScrollY;
- mOwnScrollY = 0;
+ setOwnScrollY(0);
mDontReportNextOverScroll = true;
} else {
onTop = false;
newAmount = mOwnScrollY - scrollRange;
- mOwnScrollY = scrollRange;
+ setOwnScrollY(scrollRange);
}
setOverScrollAmount(newAmount, onTop, false);
setOverScrollAmount(0.0f, onTop, true);
@@ -1850,6 +1866,19 @@
if (scrollable != mScrollable) {
mScrollable = scrollable;
setFocusable(scrollable);
+ updateForwardAndBackwardScrollability();
+ }
+ }
+
+ private void updateForwardAndBackwardScrollability() {
+ boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
+ boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
+ boolean changed = forwardScrollable != mForwardScrollable
+ || backwardsScrollable != mBackwardScrollable;
+ mForwardScrollable = forwardScrollable;
+ mBackwardScrollable = backwardsScrollable;
+ if (changed) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
}
@@ -1859,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;
}
@@ -2012,6 +2051,7 @@
if (!mIsExpanded) {
mBackgroundBounds.top = 0;
mBackgroundBounds.bottom = 0;
+ return;
}
ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
int top = 0;
@@ -2109,13 +2149,13 @@
float topAmount = getCurrentOverScrollAmount(true);
float bottomAmount = getCurrentOverScrollAmount(false);
if (velocityY < 0 && topAmount > 0) {
- mOwnScrollY -= (int) topAmount;
+ setOwnScrollY(mOwnScrollY - (int) topAmount);
mDontReportNextOverScroll = true;
setOverScrollAmount(0, true, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
* mOverflingDistance + topAmount;
} else if (velocityY > 0 && bottomAmount > 0) {
- mOwnScrollY += bottomAmount;
+ setOwnScrollY((int) (mOwnScrollY + bottomAmount));
setOverScrollAmount(0, false, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f
* getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2158,26 +2198,22 @@
*/
public void updateTopPadding(float qsHeight, boolean animate,
boolean ignoreIntrinsicPadding) {
- float start = qsHeight;
- float stackHeight = getHeight() - start;
+ int topPadding = (int) qsHeight;
int minStackHeight = getLayoutMinHeight();
- if (stackHeight <= minStackHeight) {
- float overflow = minStackHeight - stackHeight;
- stackHeight = minStackHeight;
- start = getHeight() - stackHeight;
- mTopPaddingOverflow = overflow;
+ if (topPadding + minStackHeight > getHeight()) {
+ mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
} else {
mTopPaddingOverflow = 0;
}
- setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
+ setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding),
animate);
- setStackHeight(mLastSetStackHeight);
+ setExpandedHeight(mExpandedHeight);
}
public int getLayoutMinHeight() {
int firstChildMinHeight = getFirstChildIntrinsicHeight();
return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
- mMaxLayoutHeight - mTopPadding);
+ mMaxLayoutHeight - mIntrinsicPadding);
}
public int getFirstChildIntrinsicHeight() {
@@ -2470,11 +2506,11 @@
if (endPosition <= mOwnScrollY) {
// This child is fully scrolled of the top, so we have to deduct its height from the
// scrollPosition
- mOwnScrollY -= childHeight;
+ setOwnScrollY(mOwnScrollY - childHeight);
} else if (startingPosition < mOwnScrollY) {
// This child is currently being scrolled into, set the scroll position to the start of
// this child
- mOwnScrollY = startingPosition;
+ setOwnScrollY(startingPosition);
}
}
@@ -3059,7 +3095,7 @@
public void onExpansionStopped() {
mIsExpansionChanging = false;
if (!mIsExpanded) {
- mOwnScrollY = 0;
+ setOwnScrollY(0);
mPhoneStatusBar.resetUserExpandedStates();
// lets make sure nothing is in the overlay / transient anymore
@@ -3092,7 +3128,7 @@
public void resetScrollPosition() {
mScroller.abortAnimation();
- mOwnScrollY = 0;
+ setOwnScrollY(0);
}
private void setIsExpanded(boolean isExpanded) {
@@ -3128,10 +3164,14 @@
updateScrollPositionOnExpandInBottom(view);
clampScrollPosition();
notifyHeightChangeListener(view);
+ ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) view
+ : null;
+ if (row != null && (row == mFirstVisibleBackgroundChild
+ || row.getNotificationParent() == mFirstVisibleBackgroundChild)) {
+ updateAlgorithmLayoutMinHeight();
+ }
if (needsAnimation) {
- ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) view
- : null;
requestAnimationOnViewResize(row);
}
requestChildrenUpdate();
@@ -3157,7 +3197,7 @@
}
int stackEnd = getStackEndPosition();
if (endPosition > stackEnd) {
- mOwnScrollY += endPosition - stackEnd;
+ setOwnScrollY((int) (mOwnScrollY + endPosition - stackEnd));
mDisallowScrollingInThisMotion = true;
}
}
@@ -3414,7 +3454,7 @@
}
private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
- if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
+ if (screenLocation == null || screenLocation.y < mTopPadding) {
return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
}
if (screenLocation.y > getBottomMostNotificationBottom()) {
@@ -3719,15 +3759,14 @@
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
- final int scrollRange = getScrollRange();
- if (scrollRange > 0) {
+ if (mScrollable) {
info.setScrollable(true);
- if (mScrollY > 0) {
+ if (mBackwardScrollable) {
info.addAction(
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
}
- if (mScrollY < scrollRange) {
+ if (mForwardScrollable) {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
}
@@ -3898,6 +3937,18 @@
mCurrentStackScrollState.removeViewStateForView(view);
}
+ public void setQsExpanded(boolean qsExpanded) {
+ mQsExpanded = qsExpanded;
+ updateAlgorithmLayoutMinHeight();
+ }
+
+ public void setOwnScrollY(int ownScrollY) {
+ if (ownScrollY != mOwnScrollY) {
+ mOwnScrollY = ownScrollY;
+ updateForwardAndBackwardScrollability();
+ }
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
@@ -4121,7 +4172,7 @@
onDragCancelled(animView);
// If we're on the lockscreen we want to false this.
- if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ if (isAntiFalsingNeeded()) {
mHandler.removeCallbacks(mFalsingCheck);
mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
}
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/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 5fe9296..3b14e60 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -30,6 +30,8 @@
PreferenceFragment.OnPreferenceStartFragmentCallback,
PreferenceFragment.OnPreferenceStartScreenCallback {
+ static final String ACTIVITY_ALIAS_NAME = "com.android.systemui.tuner.TunerSettingLink";
+
private static final String TAG_TUNER = "tuner";
protected void onCreate(Bundle savedInstanceState) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index e0a1547..ebc962d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -255,6 +255,12 @@
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
+
+ userContext(context).getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME),
+ enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
}
public static final boolean isTunerEnabled(Context context) {
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 b3e9f43..0000000
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
+++ /dev/null
@@ -1,53 +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_LEANBACK;
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-/**
- * Controls the picture-in-picture window for TV devices.
- */
-public class PipUI extends SystemUI {
- private boolean mSupportPip;
-
- @Override
- public void start() {
- PackageManager pm = mContext.getPackageManager();
- mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)
- && pm.hasSystemFeature(FEATURE_LEANBACK);
- 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/Android.mk b/packages/SystemUI/tests/Android.mk
index 23967aa..6038171 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -45,7 +45,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
mockito-target-minus-junit4 \
- SystemUI-proto-tags
+ SystemUI-proto \
+ SystemUI-tags
LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
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/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
new file mode 100644
index 0000000..5cb5e68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryMeterDrawableTest {
+
+ private Context mContext;
+ private Resources mResources;
+ private BatteryMeterDrawable mBatteryMeter;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResources = mContext.getResources();
+ mBatteryMeter = new BatteryMeterDrawable(mContext, 0);
+ }
+
+ @Test
+ public void testGetIntrinsicSize() {
+ assertEquals(
+ mResources.getDimensionPixelSize(R.dimen.battery_width),
+ mBatteryMeter.getIntrinsicWidth());
+ assertEquals(
+ mResources.getDimensionPixelSize(R.dimen.battery_height),
+ mBatteryMeter.getIntrinsicHeight());
+ }
+
+ @Test
+ public void testDrawNothingBeforeOnBatteryLevelChanged() {
+ final Canvas canvas = mock(Canvas.class);
+ mBatteryMeter.draw(canvas);
+ verify(canvas, never()).drawPath(any(), any());
+ verify(canvas, never()).drawText(anyString(), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void testDrawImageButNoTextIfPluggedIn() {
+ mBatteryMeter.onBatteryLevelChanged(0, true, true);
+ final Canvas canvas = mock(Canvas.class);
+ mBatteryMeter.draw(canvas);
+ verify(canvas, atLeastOnce()).drawPath(any(), any());
+ verify(canvas, never()).drawText(anyString(), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void testDrawTextIfNotPluggedIn() {
+ mBatteryMeter.onBatteryLevelChanged(0, false, false);
+ final Canvas canvas = mock(Canvas.class);
+ mBatteryMeter.draw(canvas);
+ verify(canvas, times(1)).drawText(anyString(), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void testDrawNoTextIfPowerSaveEnabled() {
+ mBatteryMeter.onBatteryLevelChanged(0, false, false);
+ mBatteryMeter.onPowerSaveChanged(true);
+ final Canvas canvas = mock(Canvas.class);
+ mBatteryMeter.draw(canvas);
+ verify(canvas, never()).drawText(anyString(), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void testDrawTextWarningAtCriticalLevel() {
+ int criticalLevel = mResources.getInteger(
+ com.android.internal.R.integer.config_criticalBatteryWarningLevel);
+ mBatteryMeter.onBatteryLevelChanged(criticalLevel, false, false);
+ final Canvas canvas = mock(Canvas.class);
+ mBatteryMeter.draw(canvas);
+ String warningString = mResources.getString(R.string.battery_meter_very_low_overlay_symbol);
+ verify(canvas, times(1)).drawText(eq(warningString), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void testDrawTextNoWarningAboveCriticalLevel() {
+ int criticalLevel = mResources.getInteger(
+ com.android.internal.R.integer.config_criticalBatteryWarningLevel);
+ mBatteryMeter.onBatteryLevelChanged(criticalLevel + 1, false, false);
+ final Canvas canvas = mock(Canvas.class);
+ mBatteryMeter.draw(canvas);
+ String warningString = mResources.getString(R.string.battery_meter_very_low_overlay_symbol);
+ verify(canvas, never()).drawText(eq(warningString), anyFloat(), anyFloat(), any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
index ab7de39..9050b83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -33,13 +33,11 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.net.Uri;
import android.os.HandlerThread;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.PluginInstanceManager.ClassLoaderFactory;
import org.junit.After;
import org.junit.Before;
@@ -64,6 +62,7 @@
private PackageManager mMockPm;
private PluginListener mMockListener;
private PluginInstanceManager mPluginInstanceManager;
+ private PluginManager mMockManager;
@Before
public void setup() throws Exception {
@@ -72,9 +71,11 @@
mContextWrapper = new MyContextWrapper(getContext());
mMockPm = mock(PackageManager.class);
mMockListener = mock(PluginListener.class);
+ mMockManager = mock(PluginManager.class);
+ when(mMockManager.getClassLoader(Mockito.any(), Mockito.any()))
+ .thenReturn(getClass().getClassLoader());
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), 1, true,
- new TestClassLoaderFactory());
+ mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true);
sMockPlugin = mock(Plugin.class);
when(sMockPlugin.getVersion()).thenReturn(1);
}
@@ -89,7 +90,7 @@
public void testNoPlugins() {
when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(
Collections.emptyList());
- mPluginInstanceManager.startListening();
+ mPluginInstanceManager.loadAll();
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
@@ -112,7 +113,7 @@
public void testPluginDestroy() {
createPlugin(); // Get into valid created state.
- mPluginInstanceManager.stopListening();
+ mPluginInstanceManager.destroy();
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
@@ -127,7 +128,7 @@
setupFakePmQuery();
when(sMockPlugin.getVersion()).thenReturn(2);
- mPluginInstanceManager.startListening();
+ mPluginInstanceManager.loadAll();
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
@@ -141,10 +142,7 @@
public void testReloadOnChange() {
createPlugin(); // Get into valid created state.
- // Send a package changed broadcast.
- Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED,
- Uri.fromParts("package", "com.android.systemui", null));
- mPluginInstanceManager.onReceive(mContextWrapper, i);
+ mPluginInstanceManager.onPackageChange("com.android.systemui");
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
@@ -164,11 +162,10 @@
public void testNonDebuggable() {
// Create a version that thinks the build is not debuggable.
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), 1, false,
- new TestClassLoaderFactory());
+ mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, false);
setupFakePmQuery();
- mPluginInstanceManager.startListening();
+ mPluginInstanceManager.loadAll();
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);;
@@ -236,19 +233,12 @@
private void createPlugin() {
setupFakePmQuery();
- mPluginInstanceManager.startListening();
+ mPluginInstanceManager.loadAll();
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
}
- private static class TestClassLoaderFactory extends ClassLoaderFactory {
- @Override
- public ClassLoader createClassLoader(String path, ClassLoader base) {
- return base;
- }
- }
-
// Real context with no registering/unregistering of receivers.
private static class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index 56e742a..4b1827d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -51,7 +51,7 @@
mMockFactory = mock(PluginInstanceManagerFactory.class);
mMockPluginInstance = mock(PluginInstanceManager.class);
when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
- Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt()))
+ Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt(), Mockito.any()))
.thenReturn(mMockPluginInstance);
mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler);
resetExceptionHandler();
@@ -62,7 +62,7 @@
public void testAddListener() {
mPluginManager.addPluginListener("myAction", mMockListener, 1);
- verify(mMockPluginInstance).startListening();
+ verify(mMockPluginInstance).loadAll();
}
@Test
@@ -70,7 +70,7 @@
mPluginManager.addPluginListener("myAction", mMockListener, 1);
mPluginManager.removePluginListener(mMockListener);
- verify(mMockPluginInstance).stopListening();
+ verify(mMockPluginInstance).destroy();
}
@Test
@@ -80,7 +80,7 @@
resetExceptionHandler();
mPluginManager.addPluginListener("myAction", mMockListener, 1);
- verify(mMockPluginInstance, Mockito.never()).startListening();
+ verify(mMockPluginInstance, Mockito.never()).loadAll();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
new file mode 100644
index 0000000..2792d8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
@@ -0,0 +1,85 @@
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.Mockito.mock;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.StatusBarIconList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatusBarIconListTest extends SysuiTestCase {
+
+ private final static String[] STATUS_BAR_SLOTS = {"aaa", "bbb", "ccc"};
+
+ @Test
+ public void testGetExistingSlot() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ assertEquals(1, statusBarIconList.getSlotIndex("bbb"));
+ assertEquals(2, statusBarIconList.getSlotIndex("ccc"));
+ }
+
+ @Test
+ public void testGetNonexistingSlot() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ assertEquals(0, statusBarIconList.getSlotIndex("aaa"));
+ assertEquals(3, statusBarIconList.size());
+ assertEquals(0, statusBarIconList.getSlotIndex("zzz")); // new content added in front
+ assertEquals(1, statusBarIconList.getSlotIndex("aaa")); // slid back
+ assertEquals(4, statusBarIconList.size());
+ }
+
+ @Test
+ public void testAddSlotSlidesIcons() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIcon = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(0, sbIcon);
+ statusBarIconList.getSlotIndex("zzz"); // new content added in front
+ assertNull(statusBarIconList.getIcon(0));
+ assertEquals(sbIcon, statusBarIconList.getIcon(1));
+ }
+
+ @Test
+ public void testGetAndSetIcon() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIconA = mock(StatusBarIcon.class);
+ StatusBarIcon sbIconB = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(0, sbIconA);
+ statusBarIconList.setIcon(1, sbIconB);
+ assertEquals(sbIconA, statusBarIconList.getIcon(0));
+ assertEquals(sbIconB, statusBarIconList.getIcon(1));
+ assertNull(statusBarIconList.getIcon(2)); // icon not set
+ }
+
+ @Test
+ public void testRemoveIcon() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIconA = mock(StatusBarIcon.class);
+ StatusBarIcon sbIconB = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(0, sbIconA);
+ statusBarIconList.setIcon(1, sbIconB);
+ statusBarIconList.removeIcon(0);
+ assertNull(statusBarIconList.getIcon(0)); // icon not set
+ }
+
+ @Test
+ public void testGetViewIndex() {
+ StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+ StatusBarIcon sbIcon = mock(StatusBarIcon.class);
+ statusBarIconList.setIcon(2, sbIcon);
+ assertEquals(0, statusBarIconList.getViewIndex(2)); // Icon for item 2 is 0th child view.
+ statusBarIconList.setIcon(0, sbIcon);
+ assertEquals(0, statusBarIconList.getViewIndex(0)); // Icon for item 0 is 0th child view,
+ assertEquals(1, statusBarIconList.getViewIndex(2)); // and item 2 is now 1st child view.
+ }
+
+}
\ No newline at end of file
diff --git a/preloaded-classes b/preloaded-classes
index 42f290e..5ddd08b 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -2077,9 +2077,6 @@
android.view.HandlerActionQueue
android.view.HandlerActionQueue$HandlerAction
android.view.HardwareLayer
-android.view.IAssetAtlas
-android.view.IAssetAtlas$Stub
-android.view.IAssetAtlas$Stub$Proxy
android.view.IGraphicsStats
android.view.IGraphicsStats$Stub
android.view.IGraphicsStats$Stub$Proxy
diff --git a/services/core/proto/ipconnectivity.proto b/proto/src/ipconnectivity.proto
similarity index 94%
rename from services/core/proto/ipconnectivity.proto
rename to proto/src/ipconnectivity.proto
index e0d7f09..29b318f 100644
--- a/services/core/proto/ipconnectivity.proto
+++ b/proto/src/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/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index da041da..cebde98 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2566,6 +2566,33 @@
// OPEN: Settings > Wireless > Manage wireless plan dialog
DIALOG_MANAGE_MOBILE_PLAN = 609;
+ // ACTION: Logs network type of the device while provisioning
+ PROVISIONING_NETWORK_TYPE = 610;
+
+ // ACTION: Logs action which triggered provisioning.
+ PROVISIONING_ACTION = 611;
+
+ // ACTION: Logs extra passed by the dpc while provisioning.
+ PROVISIONING_EXTRA = 612;
+
+ // 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/proto/src/wifi.proto b/proto/src/wifi.proto
new file mode 100644
index 0000000..9416ac1
--- /dev/null
+++ b/proto/src/wifi.proto
@@ -0,0 +1,443 @@
+/*
+ * 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 clearcut.connectivity;
+
+option java_package = "com.android.server.wifi";
+option java_outer_classname = "WifiMetricsProto";
+
+// The information about the Wifi events.
+message WifiLog {
+
+ // Session information that gets logged for every Wifi connection.
+ repeated ConnectionEvent connection_event = 1;
+
+ // Number of saved networks in the user profile.
+ optional int32 num_saved_networks = 2;
+
+ // Number of open networks in the saved networks.
+ optional int32 num_open_networks = 3;
+
+ // Number of personal networks.
+ optional int32 num_personal_networks = 4;
+
+ // Number of enterprise networks.
+ optional int32 num_enterprise_networks = 5;
+
+ // Does the user have location setting enabled.
+ optional bool is_location_enabled = 6;
+
+ // Does the user have scanning enabled.
+ optional bool is_scanning_always_enabled = 7;
+
+ // Number of times user toggled wifi using the settings menu.
+ optional int32 num_wifi_toggled_via_settings = 8;
+
+ // Number of times user toggled wifi using the airplane menu.
+ optional int32 num_wifi_toggled_via_airplane = 9;
+
+ // Number of networks added by the user.
+ optional int32 num_networks_added_by_user = 10;
+
+ // Number of networks added by applications.
+ optional int32 num_networks_added_by_apps = 11;
+
+ // Number scans that returned empty results.
+ optional int32 num_empty_scan_results = 12;
+
+ // Number scans that returned at least one result.
+ optional int32 num_non_empty_scan_results = 13;
+
+ // Number of scans that were one time.
+ optional int32 num_oneshot_scans = 14;
+
+ // Number of repeated background scans that were scheduled to the chip.
+ optional int32 num_background_scans = 15;
+
+ // Error codes that a scan can result in.
+ enum ScanReturnCode {
+
+ // Return Code is unknown.
+ SCAN_UNKNOWN = 0;
+
+ // Scan was successful.
+ SCAN_SUCCESS = 1;
+
+ // Scan was successfully started, but was interrupted.
+ SCAN_FAILURE_INTERRUPTED = 2;
+
+ // Scan failed to start because of invalid configuration
+ // (bad channel, etc).
+ SCAN_FAILURE_INVALID_CONFIGURATION = 3;
+
+ // Could not start a scan because wifi is disabled.
+ FAILURE_WIFI_DISABLED = 4;
+
+ }
+
+ // Mapping of error codes to the number of times that scans resulted
+ // in that error.
+ repeated ScanReturnEntry scan_return_entries = 16;
+
+ message ScanReturnEntry {
+
+ // Return code of the scan.
+ optional ScanReturnCode scan_return_code = 1;
+
+ // Number of entries that were found in the scan.
+ optional int32 scan_results_count = 2;
+ }
+
+ // State of the Wifi.
+ enum WifiState {
+
+ // State is unknown.
+ WIFI_UNKNOWN = 0;
+
+ // Wifi is disabled.
+ WIFI_DISABLED = 1;
+
+ // Wifi is enabled.
+ WIFI_DISCONNECTED = 2;
+
+ // Wifi is enabled and associated with an AP.
+ WIFI_ASSOCIATED = 3;
+ }
+
+ // Mapping of system state to the number of times that scans were requested in
+ // that state
+ repeated WifiSystemStateEntry wifi_system_state_entries = 17;
+
+ message WifiSystemStateEntry {
+
+ // Current WiFi state.
+ optional WifiState wifi_state = 1;
+
+ // Count of scans in state.
+ optional int32 wifi_state_count = 2;
+
+ // Is screen on.
+ optional bool is_screen_on = 3;
+ }
+
+ // Mapping of Error/Success codes to the number of background scans that resulted in it
+ repeated ScanReturnEntry background_scan_return_entries = 18;
+
+ // Mapping of system state to the number of times that Background scans were requested in that
+ // state
+ repeated WifiSystemStateEntry background_scan_request_state = 19;
+
+ // Total number of times the Watchdog of Last Resort triggered, resetting the wifi stack
+ optional int32 num_last_resort_watchdog_triggers = 20;
+
+ // Total number of networks over bad association threshold when watchdog triggered
+ optional int32 num_last_resort_watchdog_bad_association_networks_total = 21;
+
+ // Total number of networks over bad authentication threshold when watchdog triggered
+ optional int32 num_last_resort_watchdog_bad_authentication_networks_total = 22;
+
+ // Total number of networks over bad dhcp threshold when watchdog triggered
+ optional int32 num_last_resort_watchdog_bad_dhcp_networks_total = 23;
+
+ // Total number of networks over bad other threshold when watchdog triggered
+ optional int32 num_last_resort_watchdog_bad_other_networks_total = 24;
+
+ // Total count of networks seen when watchdog triggered
+ optional int32 num_last_resort_watchdog_available_networks_total = 25;
+
+ // Total count of triggers with atleast one bad association network
+ optional int32 num_last_resort_watchdog_triggers_with_bad_association = 26;
+
+ // Total count of triggers with atleast one bad authentication network
+ optional int32 num_last_resort_watchdog_triggers_with_bad_authentication = 27;
+
+ // Total count of triggers with atleast one bad dhcp network
+ optional int32 num_last_resort_watchdog_triggers_with_bad_dhcp = 28;
+
+ // Total count of triggers with atleast one bad other network
+ optional int32 num_last_resort_watchdog_triggers_with_bad_other = 29;
+
+ // Count of times connectivity watchdog confirmed pno is working
+ optional int32 num_connectivity_watchdog_pno_good = 30;
+
+ // Count of times connectivity watchdog found pno not working
+ optional int32 num_connectivity_watchdog_pno_bad = 31;
+
+ // Count of times connectivity watchdog confirmed background scan is working
+ optional int32 num_connectivity_watchdog_background_good = 32;
+
+ // Count of times connectivity watchdog found background scan not working
+ optional int32 num_connectivity_watchdog_background_bad = 33;
+
+ // The time duration represented by this wifi log, from start to end of capture
+ optional int32 record_duration_sec = 34;
+
+ // Counts the occurrences of each individual RSSI poll level
+ repeated RssiPollCount rssi_poll_rssi_count = 35;
+
+ // Total number of times WiFi connected immediately after a Last Resort Watchdog trigger,
+ // without new networks becoming available.
+ optional int32 num_last_resort_watchdog_successes = 36;
+
+ // Total number of saved hidden networks
+ optional int32 num_hidden_networks = 37;
+
+ // Total number of saved passpoint / hotspot 2.0 networks
+ optional int32 num_passpoint_networks = 38;
+
+ // Total number of scan results
+ optional int32 num_total_scan_results = 39;
+
+ // Total number of scan results for open networks
+ optional int32 num_open_network_scan_results = 40;
+
+ // Total number of scan results for personal networks
+ optional int32 num_personal_network_scan_results = 41;
+
+ // Total number of scan results for enterprise networks
+ optional int32 num_enterprise_network_scan_results = 42;
+
+ // Total number of scan results for hidden networks
+ optional int32 num_hidden_network_scan_results = 43;
+
+ // Total number of scan results for hotspot 2.0 r1 networks
+ optional int32 num_hotspot2_r1_network_scan_results = 44;
+
+ // Total number of scan results for hotspot 2.0 r2 networks
+ optional int32 num_hotspot2_r2_network_scan_results = 45;
+
+ // Total number of scans handled by framework (oneshot or otherwise)
+ optional int32 num_scans = 46;
+
+ // Counts the occurrences of each alert reason.
+ repeated AlertReasonCount alert_reason_count = 47;
+
+ // Counts the occurrences of each Wifi score
+ repeated WifiScoreCount wifi_score_count = 48;
+
+ // Histogram of Soft AP Durations
+ repeated SoftApDurationBucket soft_ap_duration = 49;
+
+ // Histogram of Soft AP ReturnCode
+ repeated SoftApReturnCodeCount soft_ap_return_code = 50;
+
+ // Histogram of the delta between scan result RSSI and RSSI polls
+ repeated RssiPollCount rssi_poll_delta_count = 51;
+}
+
+// Information that gets logged for every WiFi connection.
+message RouterFingerPrint {
+
+ enum RoamType {
+
+ // Type is unknown.
+ ROAM_TYPE_UNKNOWN = 0;
+
+ // No roaming - usually happens on a single band (2.4 GHz) router.
+ ROAM_TYPE_NONE = 1;
+
+ // Enterprise router.
+ ROAM_TYPE_ENTERPRISE = 2;
+
+ // DBDC => Dual Band Dual Concurrent essentially a router that
+ // supports both 2.4 GHz and 5 GHz bands.
+ ROAM_TYPE_DBDC = 3;
+ }
+
+ enum Auth {
+
+ // Auth is unknown.
+ AUTH_UNKNOWN = 0;
+
+ // No authentication.
+ AUTH_OPEN = 1;
+
+ // If the router uses a personal authentication.
+ AUTH_PERSONAL = 2;
+
+ // If the router is setup for enterprise authentication.
+ AUTH_ENTERPRISE = 3;
+ }
+
+ enum RouterTechnology {
+
+ // Router is unknown.
+ ROUTER_TECH_UNKNOWN = 0;
+
+ // Router Channel A.
+ ROUTER_TECH_A = 1;
+
+ // Router Channel B.
+ ROUTER_TECH_B = 2;
+
+ // Router Channel G.
+ ROUTER_TECH_G = 3;
+
+ // Router Channel N.
+ ROUTER_TECH_N = 4;
+
+ // Router Channel AC.
+ ROUTER_TECH_AC = 5;
+
+ // When the channel is not one of the above.
+ ROUTER_TECH_OTHER = 6;
+ }
+
+ optional RoamType roam_type = 1;
+
+ // Channel on which the connection takes place.
+ optional int32 channel_info = 2;
+
+ // DTIM setting of the router.
+ optional int32 dtim = 3;
+
+ // Authentication scheme of the router.
+ optional Auth authentication = 4;
+
+ // If the router is hidden.
+ optional bool hidden = 5;
+
+ // Channel information.
+ optional RouterTechnology router_technology = 6;
+
+ // whether ipv6 is supported.
+ optional bool supports_ipv6 = 7;
+
+ // If the router is a passpoint / hotspot 2.0 network
+ optional bool passpoint = 8;
+}
+
+message ConnectionEvent {
+
+ // Roam Type.
+ enum RoamType {
+
+ // Type is unknown.
+ ROAM_UNKNOWN = 0;
+
+ // No roaming.
+ ROAM_NONE = 1;
+
+ // DBDC roaming.
+ ROAM_DBDC = 2;
+
+ // Enterprise roaming.
+ ROAM_ENTERPRISE = 3;
+
+ // User selected roaming.
+ ROAM_USER_SELECTED = 4;
+
+ // Unrelated.
+ ROAM_UNRELATED = 5;
+ }
+
+ // Connectivity Level Failure.
+ enum ConnectivityLevelFailure {
+
+ // Failure is unknown.
+ HLF_UNKNOWN = 0;
+
+ // No failure.
+ HLF_NONE = 1;
+
+ // DHCP failure.
+ HLF_DHCP = 2;
+
+ // No internet connection.
+ HLF_NO_INTERNET = 3;
+
+ // No internet connection.
+ HLF_UNWANTED = 4;
+ }
+
+ // Start time of the connection.
+ optional int64 start_time_millis = 1;// [(datapol.semantic_type) = ST_TIMESTAMP];
+
+ // Duration to connect.
+ optional int32 duration_taken_to_connect_millis = 2;
+
+ // Router information.
+ optional RouterFingerPrint router_fingerprint = 3;
+
+ // RSSI at the start of the connection.
+ optional int32 signal_strength = 4;
+
+ // Roam Type.
+ optional RoamType roam_type = 5;
+
+ // Result of the connection.
+ optional int32 connection_result = 6;
+
+ // Reasons for level 2 failure (needs to be coordinated with wpa-supplicant).
+ optional int32 level_2_failure_code = 7;
+
+ // Failures that happen at the connectivity layer.
+ optional ConnectivityLevelFailure connectivity_level_failure_code = 8;
+
+ // Has bug report been taken.
+ optional bool automatic_bug_report_taken = 9;
+}
+
+// Number of occurrences of a specific RSSI poll rssi value
+message RssiPollCount {
+ // RSSI
+ optional int32 rssi = 1;
+
+ // Number of RSSI polls with 'rssi'
+ optional int32 count = 2;
+}
+
+// Number of occurrences of a specific alert reason value
+message AlertReasonCount {
+ // Alert reason
+ optional int32 reason = 1;
+
+ // Number of alerts with |reason|.
+ optional int32 count = 2;
+}
+
+// Counts the number of instances of a specific Wifi Score calculated by WifiScoreReport
+message WifiScoreCount {
+ // Wifi Score
+ optional int32 score = 1;
+
+ // Number of Wifi score reports with this score
+ optional int32 count = 2;
+}
+
+// Number of occurrences of Soft AP session durations
+message SoftApDurationBucket {
+ // Bucket covers duration : [duration_sec, duration_sec + bucket_size_sec)
+ // The (inclusive) lower bound of Soft AP session duration represented by this bucket
+ optional int32 duration_sec = 1;
+
+ // The size of this bucket
+ optional int32 bucket_size_sec = 2;
+
+ // Number of soft AP session durations that fit into this bucket
+ optional int32 count = 3;
+}
+
+// Number of occurrences of a soft AP session return code
+message SoftApReturnCodeCount {
+ // Return code of the soft AP session
+ optional int32 return_code = 1;
+
+ // Occurrences of this soft AP return code
+ optional int32 count = 2;
+}
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index 0658620..bf3681b 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -28,7 +28,7 @@
frameworks/base/libs/hwui \
$(rs_generated_include_dir)
-LOCAL_CFLAGS += -Wno-unused-parameter -std=c++11
+LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
LOCAL_ADDITIONAL_DEPENDENCIES := $(addprefix $(rs_generated_include_dir)/,rsgApiFuncDecl.h)
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 06c4350..af370ff 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -644,7 +644,7 @@
in_allocs[2] = (RsAllocation)C;
rsScriptForEachMulti((RsContext)con, (RsScript)id, 0,
- in_allocs, sizeof(in_allocs), nullptr,
+ in_allocs, NELEM(in_allocs), nullptr,
&call, sizeof(call), nullptr, 0);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0b83e66..4819c0a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -64,6 +64,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
@@ -498,20 +499,6 @@
}
@Override
- public void sendAccessibilityEvents(ParceledListSlice events, int userId) {
- List<AccessibilityEvent> a11yEvents = events.getList();
- // Grab the lock once for the entire batch
- synchronized (mLock) {
- int numEventsToProcess = Math.min(a11yEvents.size(),
- AccessibilityManager.MAX_A11Y_EVENTS_PER_SERVICE_CALL);
- for (int i = 0; i < numEventsToProcess; i++) {
- AccessibilityEvent event = a11yEvents.get(i);
- sendAccessibilityEvent(event, userId);
- }
- }
- }
-
- @Override
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
@@ -1315,14 +1302,8 @@
private void updateServicesLocked(UserState userState) {
Map<ComponentName, Service> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
- boolean isUnlockingOrUnlocked;
- final long identity = Binder.clearCallingIdentity();
- try {
- isUnlockingOrUnlocked = mContext.getSystemService(UserManager.class)
+ boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
.isUserUnlockingOrUnlocked(userState.mUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 497eac9..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
@@ -6057,7 +6057,11 @@
// the app developer's cert, so they're different on every
// device.
if (signaturesMatch(sigs, pkgInfo)) {
- if (pkgInfo.versionCode >= version) {
+ if ((pkgInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
+ Slog.i(TAG, "Package has restoreAnyVersion; taking data");
+ policy = RestorePolicy.ACCEPT;
+ } else if (pkgInfo.versionCode >= version) {
Slog.i(TAG, "Sig + version match; taking data");
policy = RestorePolicy.ACCEPT;
} else {
@@ -6964,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
@@ -7479,7 +7483,11 @@
// the app developer's cert, so they're different on every
// device.
if (signaturesMatch(sigs, pkgInfo)) {
- if (pkgInfo.versionCode >= version) {
+ if ((pkgInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
+ Slog.i(TAG, "Package has restoreAnyVersion; taking data");
+ policy = RestorePolicy.ACCEPT;
+ } else if (pkgInfo.versionCode >= version) {
Slog.i(TAG, "Sig + version match; taking data");
policy = RestorePolicy.ACCEPT;
} else {
@@ -8382,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/Android.mk b/services/core/Android.mk
index 58f2074..11586ee 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -8,7 +8,6 @@
LOCAL_SRC_FILES += \
$(call all-java-files-under,java) \
- $(call all-proto-files-under, proto) \
java/com/android/server/EventLogTags.logtags \
java/com/android/server/am/EventLogTags.logtags \
../../../../system/netd/server/binder/android/net/INetd.aidl \
@@ -17,9 +16,13 @@
LOCAL_AIDL_INCLUDES += \
system/netd/server/binder
-LOCAL_JAVA_LIBRARIES := services.net telephony-common
+LOCAL_JAVA_LIBRARIES := \
+ services.net \
+ telephony-common \
+ android.hardware.power@1.0-java \
+ android.hardware.light@2.0-java
+
LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 238bf92..7c2eea3f 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -51,6 +51,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.storage.MountServiceInternal;
@@ -1787,8 +1788,9 @@
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell(this, this)).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell(this, this)).exec(this, in, out, err, args, callback, resultReceiver);
}
static void dumpCommandHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java
deleted file mode 100644
index b0f6048..0000000
--- a/services/core/java/com/android/server/AssetAtlasService.java
+++ /dev/null
@@ -1,717 +0,0 @@
-/*
- * Copyright (C) 2013 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;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Atlas;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.drawable.Drawable;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.view.GraphicBuffer;
-import android.view.IAssetAtlas;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * This service is responsible for packing preloaded bitmaps into a single
- * atlas texture. The resulting texture can be shared across processes to
- * reduce overall memory usage.
- *
- * @hide
- */
-public class AssetAtlasService extends IAssetAtlas.Stub {
- /**
- * Name of the <code>AssetAtlasService</code>.
- */
- public static final String ASSET_ATLAS_SERVICE = "assetatlas";
-
- private static final String LOG_TAG = "AssetAtlas";
-
- // Turns debug logs on/off. Debug logs are kept to a minimum and should
- // remain on to diagnose issues
- private static final boolean DEBUG_ATLAS = true;
-
- // When set to true the content of the atlas will be saved to disk
- // in /data/system/atlas.png. The shared GraphicBuffer may be empty
- private static final boolean DEBUG_ATLAS_TEXTURE = false;
-
- // Minimum size in pixels to consider for the resulting texture
- private static final int MIN_SIZE = 512;
- // Maximum size in pixels to consider for the resulting texture
- private static final int MAX_SIZE = 2048;
- // Increment in number of pixels between size variants when looking
- // for the best texture dimensions
- private static final int STEP = 64;
-
- // This percentage of the total number of pixels represents the minimum
- // number of pixels we want to be able to pack in the atlas
- private static final float PACKING_THRESHOLD = 0.8f;
-
- // Defines the number of int fields used to represent a single entry
- // in the atlas map. This number defines the size of the array returned
- // by the getMap(). See the mAtlasMap field for more information
- private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 3;
-
- // Specifies how our GraphicBuffer will be used. To get proper swizzling
- // the buffer will be written to using OpenGL (from JNI) so we can leave
- // the software flag set to "never"
- private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER |
- GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE;
-
- // This boolean is set to true if an atlas was successfully
- // computed and rendered
- private final AtomicBoolean mAtlasReady = new AtomicBoolean(false);
-
- private final Context mContext;
-
- // Version name of the current build, used to identify changes to assets list
- private final String mVersionName;
-
- // Holds the atlas' data. This buffer can be mapped to
- // OpenGL using an EGLImage
- private GraphicBuffer mBuffer;
-
- // Describes how bitmaps are placed in the atlas. Each bitmap is
- // represented by several entries in the array:
- // long0: SkBitmap*, the native bitmap object
- // long1: x position
- // long2: y position
- private long[] mAtlasMap;
-
- /**
- * Creates a new service. Upon creating, the service will gather the list of
- * assets to consider for packing into the atlas and spawn a new thread to
- * start the packing work.
- *
- * @param context The context giving access to preloaded resources
- */
- public AssetAtlasService(Context context) {
- mContext = context;
- mVersionName = queryVersionName(context);
-
- Collection<Bitmap> bitmaps = new HashSet<Bitmap>(300);
- int totalPixelCount = 0;
-
- // We only care about drawables that hold bitmaps
- final Resources resources = context.getResources();
- final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
-
- final int count = drawables.size();
- for (int i = 0; i < count; i++) {
- try {
- totalPixelCount += drawables.valueAt(i).addAtlasableBitmaps(bitmaps);
- } catch (Throwable t) {
- Log.e("AssetAtlas", "Failed to fetch preloaded drawable state", t);
- throw t;
- }
- }
-
- ArrayList<Bitmap> sortedBitmaps = new ArrayList<Bitmap>(bitmaps);
- // Our algorithms perform better when the bitmaps are first sorted
- // The comparator will sort the bitmap by width first, then by height
- Collections.sort(sortedBitmaps, new Comparator<Bitmap>() {
- @Override
- public int compare(Bitmap b1, Bitmap b2) {
- if (b1.getWidth() == b2.getWidth()) {
- return b2.getHeight() - b1.getHeight();
- }
- return b2.getWidth() - b1.getWidth();
- }
- });
-
- // Kick off the packing work on a worker thread
- new Thread(new Renderer(sortedBitmaps, totalPixelCount)).start();
- }
-
- /**
- * Queries the version name stored in framework's AndroidManifest.
- * The version name can be used to identify possible changes to
- * framework resources.
- *
- * @see #getBuildIdentifier(String)
- */
- private static String queryVersionName(Context context) {
- try {
- String packageName = context.getPackageName();
- PackageInfo info = context.getPackageManager().getPackageInfo(packageName,
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
- return info.versionName;
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(LOG_TAG, "Could not get package info", e);
- }
- return null;
- }
-
- /**
- * Callback invoked by the server thread to indicate we can now run
- * 3rd party code.
- */
- public void systemRunning() {
- }
-
- /**
- * The renderer does all the work:
- */
- private class Renderer implements Runnable {
- private final ArrayList<Bitmap> mBitmaps;
- private final int mPixelCount;
-
- Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) {
- mBitmaps = bitmaps;
- mPixelCount = pixelCount;
- }
-
- /**
- * 1. On first boot or after every update, brute-force through all the
- * possible atlas configurations and look for the best one (maximimize
- * number of packed assets and minimize texture size)
- * a. If a best configuration was computed, write it out to disk for
- * future use
- * 2. Read best configuration from disk
- * 3. Compute the packing using the best configuration
- * 4. Allocate a GraphicBuffer
- * 5. Render assets in the buffer
- */
- @Override
- public void run() {
- Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName);
- if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config);
-
- if (config != null) {
- mBuffer = GraphicBuffer.create(config.width, config.height,
- PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE);
-
- if (mBuffer != null) {
- Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags);
- if (renderAtlas(mBuffer, atlas, config.count)) {
- mAtlasReady.set(true);
- }
- }
- }
- }
-
- /**
- * Renders a list of bitmaps into the atlas. The position of each bitmap
- * was decided by the packing algorithm and will be honored by this
- * method.
- *
- * @param buffer The buffer to render the atlas entries into
- * @param atlas The atlas to pack the bitmaps into
- * @param packCount The number of bitmaps that will be packed in the atlas
- *
- * @return true if the atlas was rendered, false otherwise
- */
- @SuppressWarnings("MismatchedReadAndWriteOfArray")
- private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) {
- // Use a Source blend mode to improve performance, the target bitmap
- // will be zero'd out so there's no need to waste time applying blending
- final Paint paint = new Paint();
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-
- // We always render the atlas into a bitmap. This bitmap is then
- // uploaded into the GraphicBuffer using OpenGL to swizzle the content
- final Bitmap atlasBitmap = Bitmap.createBitmap(
- buffer.getWidth(), buffer.getHeight(), Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(atlasBitmap);
-
- final Atlas.Entry entry = new Atlas.Entry();
-
- mAtlasMap = new long[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT];
- long[] atlasMap = mAtlasMap;
- int mapIndex = 0;
-
- boolean result = false;
- final long startRender = System.nanoTime();
- final int count = mBitmaps.size();
-
- for (int i = 0; i < count; i++) {
- final Bitmap bitmap = mBitmaps.get(i);
- if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
- // We have more bitmaps to pack than the current configuration
- // says, we were most likely not able to detect a change in the
- // list of preloaded drawables, abort and delete the configuration
- if (mapIndex >= mAtlasMap.length) {
- deleteDataFile();
- break;
- }
-
- canvas.save();
- canvas.translate(entry.x, entry.y);
- canvas.drawBitmap(bitmap, 0.0f, 0.0f, null);
- canvas.restore();
- atlasMap[mapIndex++] = bitmap.refSkPixelRef();
- atlasMap[mapIndex++] = entry.x;
- atlasMap[mapIndex++] = entry.y;
- }
- }
-
- final long endRender = System.nanoTime();
- releaseCanvas(canvas, atlasBitmap);
- result = nUploadAtlas(buffer, atlasBitmap);
- atlasBitmap.recycle();
- final long endUpload = System.nanoTime();
-
- if (DEBUG_ATLAS) {
- float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f;
- float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f;
- Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)",
- renderDuration + uploadDuration, renderDuration, uploadDuration));
- }
-
- return result;
- }
-
- /**
- * Releases the canvas used to render into the buffer. Calling this method
- * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE}
- * is turend on, calling this method will write the content of the atlas
- * to disk in /data/system/atlas.png for debugging.
- */
- private void releaseCanvas(Canvas canvas, Bitmap atlasBitmap) {
- canvas.setBitmap(null);
- if (DEBUG_ATLAS_TEXTURE) {
-
- File systemDirectory = new File(Environment.getDataDirectory(), "system");
- File dataFile = new File(systemDirectory, "atlas.png");
-
- try {
- FileOutputStream out = new FileOutputStream(dataFile);
- atlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.close();
- } catch (FileNotFoundException e) {
- // Ignore
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- }
-
- private static native boolean nUploadAtlas(GraphicBuffer buffer, Bitmap bitmap);
-
- @Override
- public boolean isCompatible(int ppid) {
- return ppid == android.os.Process.myPpid();
- }
-
- @Override
- public GraphicBuffer getBuffer() throws RemoteException {
- return mAtlasReady.get() ? mBuffer : null;
- }
-
- @Override
- public long[] getMap() throws RemoteException {
- return mAtlasReady.get() ? mAtlasMap : null;
- }
-
- /**
- * Finds the best atlas configuration to pack the list of supplied bitmaps.
- * This method takes advantage of multi-core systems by spawning a number
- * of threads equal to the number of available cores.
- */
- private static Configuration computeBestConfiguration(
- ArrayList<Bitmap> bitmaps, int pixelCount) {
- if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration...");
-
- long begin = System.nanoTime();
- List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>());
-
- // Don't bother with an extra thread if there's only one processor
- int cpuCount = Runtime.getRuntime().availableProcessors();
- if (cpuCount == 1) {
- new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run();
- } else {
- int start = MIN_SIZE + (cpuCount - 1) * STEP;
- int end = MAX_SIZE;
- int step = STEP * cpuCount;
-
- final CountDownLatch signal = new CountDownLatch(cpuCount);
-
- for (int i = 0; i < cpuCount; i++, start -= STEP, end -= STEP) {
- ComputeWorker worker = new ComputeWorker(start, end, step,
- bitmaps, pixelCount, results, signal);
- new Thread(worker, "Atlas Worker #" + (i + 1)).start();
- }
-
- boolean isAllWorkerFinished;
- try {
- isAllWorkerFinished = signal.await(10, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Log.w(LOG_TAG, "Could not complete configuration computation");
- return null;
- }
-
- if (!isAllWorkerFinished) {
- // We have to abort here, otherwise the async updates on "results" would crash the
- // sort later.
- Log.w(LOG_TAG, "Could not complete configuration computation before timeout.");
- return null;
- }
- }
-
- // Maximize the number of packed bitmaps, minimize the texture size
- Collections.sort(results, new Comparator<WorkerResult>() {
- @Override
- public int compare(WorkerResult r1, WorkerResult r2) {
- int delta = r2.count - r1.count;
- if (delta != 0) return delta;
- return r1.width * r1.height - r2.width * r2.height;
- }
- });
-
- if (DEBUG_ATLAS) {
- float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f;
- Log.d(LOG_TAG, String.format("Found best atlas configuration (out of %d) in %.2fs",
- results.size(), delay));
- }
-
- WorkerResult result = results.get(0);
- return new Configuration(result.type, result.width, result.height, result.count);
- }
-
- /**
- * Returns the path to the file containing the best computed
- * atlas configuration.
- */
- private static File getDataFile() {
- File systemDirectory = new File(Environment.getDataDirectory(), "system");
- return new File(systemDirectory, "framework_atlas.config");
- }
-
- private static void deleteDataFile() {
- Log.w(LOG_TAG, "Current configuration inconsistent with assets list");
- if (!getDataFile().delete()) {
- Log.w(LOG_TAG, "Could not delete the current configuration");
- }
- }
-
- private File getFrameworkResourcesFile() {
- return new File(mContext.getApplicationInfo().sourceDir);
- }
-
- /**
- * Returns the best known atlas configuration. This method will either
- * read the configuration from disk or start a brute-force search
- * and save the result out to disk.
- */
- private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount,
- String versionName) {
- Configuration config = null;
-
- final File dataFile = getDataFile();
- if (dataFile.exists()) {
- config = readConfiguration(dataFile, versionName);
- }
-
- if (config == null) {
- config = computeBestConfiguration(bitmaps, pixelCount);
- if (config != null) writeConfiguration(config, dataFile, versionName);
- }
-
- return config;
- }
-
- /**
- * Writes the specified atlas configuration to the specified file.
- */
- private void writeConfiguration(Configuration config, File file, String versionName) {
- BufferedWriter writer = null;
- try {
- writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
- writer.write(getBuildIdentifier(versionName));
- writer.newLine();
- writer.write(config.type.toString());
- writer.newLine();
- writer.write(String.valueOf(config.width));
- writer.newLine();
- writer.write(String.valueOf(config.height));
- writer.newLine();
- writer.write(String.valueOf(config.count));
- writer.newLine();
- writer.write(String.valueOf(config.flags));
- writer.newLine();
- } catch (FileNotFoundException e) {
- Log.w(LOG_TAG, "Could not write " + file, e);
- } catch (IOException e) {
- Log.w(LOG_TAG, "Could not write " + file, e);
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- }
-
- /**
- * Reads an atlas configuration from the specified file. This method
- * returns null if an error occurs or if the configuration is invalid.
- */
- private Configuration readConfiguration(File file, String versionName) {
- BufferedReader reader = null;
- Configuration config = null;
- try {
- reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
-
- if (checkBuildIdentifier(reader, versionName)) {
- Atlas.Type type = Atlas.Type.valueOf(reader.readLine());
- int width = readInt(reader, MIN_SIZE, MAX_SIZE);
- int height = readInt(reader, MIN_SIZE, MAX_SIZE);
- int count = readInt(reader, 0, Integer.MAX_VALUE);
- int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE);
-
- config = new Configuration(type, width, height, count, flags);
- }
- } catch (IllegalArgumentException e) {
- Log.w(LOG_TAG, "Invalid parameter value in " + file, e);
- } catch (FileNotFoundException e) {
- Log.w(LOG_TAG, "Could not read " + file, e);
- } catch (IOException e) {
- Log.w(LOG_TAG, "Could not read " + file, e);
- } finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- return config;
- }
-
- private static int readInt(BufferedReader reader, int min, int max) throws IOException {
- return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine())));
- }
-
- /**
- * Compares the next line in the specified buffered reader to the current
- * build identifier. Returns whether the two values are equal.
- *
- * @see #getBuildIdentifier(String)
- */
- private boolean checkBuildIdentifier(BufferedReader reader, String versionName)
- throws IOException {
- String deviceBuildId = getBuildIdentifier(versionName);
- String buildId = reader.readLine();
- return deviceBuildId.equals(buildId);
- }
-
- /**
- * Returns an identifier for the current build that can be used to detect
- * likely changes to framework resources. The build identifier is made of
- * several distinct values:
- *
- * build fingerprint/framework version name/file size of framework resources apk
- *
- * Only the build fingerprint should be necessary on user builds but
- * the other values are useful to detect changes on eng builds during
- * development.
- *
- * This identifier does not attempt to be exact: a new identifier does not
- * necessarily mean the preloaded drawables have changed. It is important
- * however that whenever the list of preloaded drawables changes, this
- * identifier changes as well.
- *
- * @see #checkBuildIdentifier(java.io.BufferedReader, String)
- */
- private String getBuildIdentifier(String versionName) {
- return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' +
- String.valueOf(getFrameworkResourcesFile().length());
- }
-
- /**
- * Atlas configuration. Specifies the algorithm, dimensions and flags to use.
- */
- private static class Configuration {
- final Atlas.Type type;
- final int width;
- final int height;
- final int count;
- final int flags;
-
- Configuration(Atlas.Type type, int width, int height, int count) {
- this(type, width, height, count, Atlas.FLAG_DEFAULTS);
- }
-
- Configuration(Atlas.Type type, int width, int height, int count, int flags) {
- this.type = type;
- this.width = width;
- this.height = height;
- this.count = count;
- this.flags = flags;
- }
-
- @Override
- public String toString() {
- return type.toString() + " (" + width + "x" + height + ") flags=0x" +
- Integer.toHexString(flags) + " count=" + count;
- }
- }
-
- /**
- * Used during the brute-force search to gather information about each
- * variant of the packing algorithm.
- */
- private static class WorkerResult {
- Atlas.Type type;
- int width;
- int height;
- int count;
-
- WorkerResult(Atlas.Type type, int width, int height, int count) {
- this.type = type;
- this.width = width;
- this.height = height;
- this.count = count;
- }
-
- @Override
- public String toString() {
- return String.format("%s %dx%d", type.toString(), width, height);
- }
- }
-
- /**
- * A compute worker will try a finite number of variations of the packing
- * algorithms and save the results in a supplied list.
- */
- private static class ComputeWorker implements Runnable {
- private final int mStart;
- private final int mEnd;
- private final int mStep;
- private final List<Bitmap> mBitmaps;
- private final List<WorkerResult> mResults;
- private final CountDownLatch mSignal;
- private final int mThreshold;
-
- /**
- * Creates a new compute worker to brute-force through a range of
- * packing algorithms variants.
- *
- * @param start The minimum texture width to try
- * @param end The maximum texture width to try
- * @param step The number of pixels to increment the texture width by at each step
- * @param bitmaps The list of bitmaps to pack in the atlas
- * @param pixelCount The total number of pixels occupied by the list of bitmaps
- * @param results The list of results in which to save the brute-force search results
- * @param signal Latch to decrement when this worker is done, may be null
- */
- ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount,
- List<WorkerResult> results, CountDownLatch signal) {
- mStart = start;
- mEnd = end;
- mStep = step;
- mBitmaps = bitmaps;
- mResults = results;
- mSignal = signal;
-
- // Minimum number of pixels we want to be able to pack
- int threshold = (int) (pixelCount * PACKING_THRESHOLD);
- // Make sure we can find at least one configuration
- while (threshold > MAX_SIZE * MAX_SIZE) {
- threshold >>= 1;
- }
- mThreshold = threshold;
- }
-
- @Override
- public void run() {
- if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName());
-
- Atlas.Entry entry = new Atlas.Entry();
-
- for (int width = mEnd; width > mStart; width -= mStep) {
- for (int height = MAX_SIZE; height > MIN_SIZE; height -= STEP) {
- // If the atlas is not big enough, skip it
- if (width * height <= mThreshold) continue;
-
- boolean packSuccess = false;
-
- for (Atlas.Type type : Atlas.Type.values()) {
- final int count = packBitmaps(type, width, height, entry);
- if (count > 0) {
- mResults.add(new WorkerResult(type, width, height, count));
- if (count == mBitmaps.size()) {
- // If we were able to pack everything let's stop here
- // Changing the type further won't make things better
- packSuccess = true;
- break;
- }
- }
- }
-
- // If we were not able to pack everything let's stop here
- // Decreasing the height further won't make things better
- if (!packSuccess) {
- break;
- }
- }
- }
-
- if (mSignal != null) {
- mSignal.countDown();
- }
- }
-
- private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) {
- int total = 0;
- Atlas atlas = new Atlas(type, width, height);
-
- final int count = mBitmaps.size();
- for (int i = 0; i < count; i++) {
- final Bitmap bitmap = mBitmaps.get(i);
- if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
- total++;
- }
- }
-
- return total;
- }
- }
-}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 6b51721..d2cfb6d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -20,6 +20,7 @@
import android.os.BatteryStats;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import com.android.internal.app.IBatteryStats;
import com.android.server.am.BatteryStatsService;
@@ -789,7 +790,7 @@
pw.println(" technology: " + mBatteryProps.batteryTechnology);
} else {
Shell shell = new Shell();
- shell.exec(mBinderService, null, fd, null, args, new ResultReceiver(null));
+ shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
}
}
}
@@ -875,8 +876,9 @@
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 3ad71ea..6456048 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
@@ -222,6 +222,7 @@
mBluetoothLock.readLock().lock();
if (mBluetooth != null) {
mBluetooth.onBrEdrDown();
+ mEnable = false;
mEnableExternal = false;
}
} catch (RemoteException e) {
@@ -275,6 +276,7 @@
mContext.registerReceiver(mReceiver, filter);
loadStoredNameAndAddress();
if (isBluetoothPersistedStateOn()) {
+ if (DBG) Slog.d(TAG, "Startup: Bluetooth persisted state is ON.");
mEnableExternal = true;
}
@@ -301,8 +303,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 +322,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);
@@ -454,14 +459,16 @@
class ClientDeathRecipient implements IBinder.DeathRecipient {
public void binderDied() {
- if (DBG) Slog.d(TAG, "Binder is dead - unregister Ble App");
+ if (DBG) Slog.d(TAG, "Binder is dead - unregister Ble App");
if (mBleAppCount > 0) --mBleAppCount;
if (mBleAppCount == 0) {
if (DBG) Slog.d(TAG, "Disabling LE only mode after application crash");
try {
mBluetoothLock.readLock().lock();
- if (mBluetooth != null) {
+ if (mBluetooth != null &&
+ mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ mEnable = false;
mBluetooth.onBrEdrDown();
}
} catch (RemoteException e) {
@@ -478,6 +485,9 @@
@Override
public boolean isBleScanAlwaysAvailable() {
+ if (isAirplaneModeOn() && !mEnable) {
+ return false;
+ }
try {
return (Settings.Global.getInt(mContentResolver,
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE)) != 0;
@@ -1430,7 +1440,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 +1752,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 a2f3be7..de70026 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -78,6 +78,7 @@
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
+import android.net.util.AvoidBadWifiTracker;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -131,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;
@@ -403,11 +405,6 @@
private static final int EVENT_REQUEST_LINKPROPERTIES = 32;
private static final int EVENT_REQUEST_NETCAPABILITIES = 33;
- /**
- * Used internally to (re)configure avoid bad wifi setting.
- */
- private static final int EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI = 34;
-
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -501,6 +498,9 @@
private final IpConnectivityLog mMetricsLog;
+ @VisibleForTesting
+ final AvoidBadWifiTracker mAvoidBadWifiTracker;
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -816,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);
@@ -863,14 +864,8 @@
LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
- intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- mHandler.sendEmptyMessage(EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI);
- }
- }, UserHandle.ALL, intentFilter, null, null);
- updateAvoidBadWifi();
+ mAvoidBadWifiTracker = createAvoidBadWifiTracker(
+ mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
}
private NetworkRequest createInternetRequestForTransport(
@@ -921,12 +916,6 @@
mSettingsObserver.observe(
Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
-
- // Watch for whether to automatically switch away from wifi networks that lose Internet
- // access.
- mSettingsObserver.observe(
- Settings.Global.getUriFor(Settings.Global.NETWORK_AVOID_BAD_WIFI),
- EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI);
}
private synchronized int nextNetworkRequestId() {
@@ -2268,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) {
@@ -2293,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:
@@ -2794,36 +2797,23 @@
PROMPT_UNVALIDATED_DELAY_MS);
}
- private boolean mAvoidBadWifi = true;
-
public boolean avoidBadWifi() {
- return mAvoidBadWifi;
+ return mAvoidBadWifiTracker.currentValue();
}
- @VisibleForTesting
- /** Whether the device or carrier configuration disables avoiding bad wifi by default. */
- public boolean configRestrictsAvoidBadWifi() {
- return mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0;
+ private void rematchForAvoidBadWifiUpdate() {
+ rematchAllNetworksAndRequests(null, 0);
+ for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
+ if (nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ sendUpdatedScoreToFactories(nai);
+ }
+ }
}
- /** Whether we should display a notification when wifi becomes unvalidated. */
- public boolean shouldNotifyWifiUnvalidated() {
- return configRestrictsAvoidBadWifi() &&
- Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.NETWORK_AVOID_BAD_WIFI) == null;
- }
-
- private boolean updateAvoidBadWifi() {
- boolean settingAvoidBadWifi = "1".equals(Settings.Global.getString(
- mContext.getContentResolver(), Settings.Global.NETWORK_AVOID_BAD_WIFI));
-
- boolean prev = mAvoidBadWifi;
- mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
- return mAvoidBadWifi != prev;
- }
-
+ // TODO: Evaluate whether this is of interest to other consumers of
+ // AvoidBadWifiTracker and worth moving out of here.
private void dumpAvoidBadWifiSettings(IndentingPrintWriter pw) {
- boolean configRestrict = configRestrictsAvoidBadWifi();
+ final boolean configRestrict = mAvoidBadWifiTracker.configRestrictsAvoidBadWifi();
if (!configRestrict) {
pw.println("Bad Wi-Fi avoidance: unrestricted");
return;
@@ -2833,8 +2823,7 @@
pw.increaseIndent();
pw.println("Config restrict: " + configRestrict);
- String value = Settings.Global.getString(
- mContext.getContentResolver(), Settings.Global.NETWORK_AVOID_BAD_WIFI);
+ final String value = mAvoidBadWifiTracker.getSettingsValue();
String description;
// Can't use a switch statement because strings are legal case labels, but null is not.
if ("0".equals(value)) {
@@ -2897,17 +2886,12 @@
showValidationNotification(nai, NotificationType.NO_INTERNET);
}
- // TODO: Delete this like updateMobileDataAlwaysOn above.
- @VisibleForTesting
- void updateNetworkAvoidBadWifi() {
- mHandler.sendEmptyMessage(EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI);
- }
-
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
NetworkCapabilities nc = nai.networkCapabilities;
if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
- if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && shouldNotifyWifiUnvalidated()) {
+ if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
+ mAvoidBadWifiTracker.shouldNotifyWifiUnvalidated()) {
showValidationNotification(nai, NotificationType.LOST_INTERNET);
}
}
@@ -3002,18 +2986,6 @@
handleMobileDataAlwaysOn();
break;
}
- case EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI: {
- if (updateAvoidBadWifi()) {
- rematchAllNetworksAndRequests(null, 0);
- for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
- if (nai.networkCapabilities.hasTransport(
- NetworkCapabilities.TRANSPORT_WIFI)) {
- sendUpdatedScoreToFactories(nai);
- }
- }
- }
- break;
- }
case EVENT_REQUEST_LINKPROPERTIES:
handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1);
break;
@@ -5502,7 +5474,7 @@
@Override
public String getCaptivePortalServerUrl() {
- return NetworkMonitor.getCaptivePortalServerUrl(mContext);
+ return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
}
@Override
@@ -5577,6 +5549,11 @@
}
@VisibleForTesting
+ AvoidBadWifiTracker createAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
+ return new AvoidBadWifiTracker(c, h, r);
+ }
+
+ @VisibleForTesting
public WakeupMessage makeWakeupMessage(Context c, Handler h, String s, int cmd, Object obj) {
return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 6b73fec..466633a 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -60,6 +60,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -1232,8 +1233,8 @@
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
}
@@ -2725,12 +2726,12 @@
}
}
} else if ("whitelist".equals(cmd)) {
- long token = Binder.clearCallingIdentity();
- try {
- String arg = shell.getNextArg();
- if (arg != null) {
- getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.DEVICE_POWER, null);
+ String arg = shell.getNextArg();
+ if (arg != null) {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ long token = Binder.clearCallingIdentity();
+ try {
do {
if (arg.length() < 1 || (arg.charAt(0) != '-'
&& arg.charAt(0) != '+' && arg.charAt(0) != '=')) {
@@ -2753,30 +2754,30 @@
pw.println(getPowerSaveWhitelistAppInternal(pkg));
}
} while ((arg=shell.getNextArg()) != null);
- } else {
- synchronized (this) {
- for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
- pw.print("system-excidle,");
- pw.print(mPowerSaveWhitelistAppsExceptIdle.keyAt(j));
- pw.print(",");
- pw.println(mPowerSaveWhitelistAppsExceptIdle.valueAt(j));
- }
- for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
- pw.print("system,");
- pw.print(mPowerSaveWhitelistApps.keyAt(j));
- pw.print(",");
- pw.println(mPowerSaveWhitelistApps.valueAt(j));
- }
- for (int j=0; j<mPowerSaveWhitelistUserApps.size(); j++) {
- pw.print("user,");
- pw.print(mPowerSaveWhitelistUserApps.keyAt(j));
- pw.print(",");
- pw.println(mPowerSaveWhitelistUserApps.valueAt(j));
- }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ synchronized (this) {
+ for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
+ pw.print("system-excidle,");
+ pw.print(mPowerSaveWhitelistAppsExceptIdle.keyAt(j));
+ pw.print(",");
+ pw.println(mPowerSaveWhitelistAppsExceptIdle.valueAt(j));
+ }
+ for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+ pw.print("system,");
+ pw.print(mPowerSaveWhitelistApps.keyAt(j));
+ pw.print(",");
+ pw.println(mPowerSaveWhitelistApps.valueAt(j));
+ }
+ for (int j=0; j<mPowerSaveWhitelistUserApps.size(); j++) {
+ pw.print("user,");
+ pw.print(mPowerSaveWhitelistUserApps.keyAt(j));
+ pw.print(",");
+ pw.println(mPowerSaveWhitelistUserApps.valueAt(j));
}
}
- } finally {
- Binder.restoreCallingIdentity(token);
}
} else if ("tempwhitelist".equals(cmd)) {
String opt;
@@ -2838,7 +2839,8 @@
shell.userId = userId;
String[] newArgs = new String[args.length-i];
System.arraycopy(args, i, newArgs, 0, args.length-i);
- shell.exec(mBinderService, null, fd, null, newArgs, new ResultReceiver(null));
+ shell.exec(mBinderService, null, fd, null, newArgs, null,
+ new ResultReceiver(null));
return;
}
}
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/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 8f16504..e64aa16 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -77,6 +77,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.PhoneStateListener;
@@ -1699,6 +1700,7 @@
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
try {
mConnector.execute("bandwidth", suffix + chain, uid);
if (enable) {
@@ -1708,6 +1710,8 @@
}
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
}
@@ -1730,6 +1734,7 @@
Log.w(TAG, "setDataSaverMode(): already " + mDataSaverMode);
return true;
}
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "bandwidthEnableDataSaver");
try {
final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
if (changed) {
@@ -1741,6 +1746,8 @@
} catch (RemoteException e) {
Log.w(TAG, "setDataSaverMode(" + enable + "): netd command failed", e);
return false;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
}
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 276687f..3c8c699 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -21,6 +21,7 @@
import android.net.LocalSocketAddress;
import android.os.IRecoverySystem;
import android.os.IRecoverySystemProgressListener;
+import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -50,8 +51,15 @@
// The socket at /dev/socket/uncrypt to communicate with uncrypt.
private static final String UNCRYPT_SOCKET = "uncrypt";
+ // The init services that communicate with /system/bin/uncrypt.
+ private static final String INIT_SERVICE_UNCRYPT = "init.svc.uncrypt";
+ private static final String INIT_SERVICE_SETUP_BCB = "init.svc.setup-bcb";
+ private static final String INIT_SERVICE_CLEAR_BCB = "init.svc.clear-bcb";
+
private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
+ private static final Object sRequestLock = new Object();
+
private Context mContext;
public RecoverySystemService(Context context) {
@@ -69,95 +77,155 @@
public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+ synchronized (sRequestLock) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
- // Write the filename into UNCRYPT_PACKAGE_FILE to be read by
- // uncrypt.
- RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
-
- try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) {
- uncryptFile.write(filename + "\n");
- } catch (IOException e) {
- Slog.e(TAG, "IOException when writing \"" + RecoverySystem.UNCRYPT_PACKAGE_FILE +
- "\": ", e);
- return false;
- }
-
- // Trigger uncrypt via init.
- SystemProperties.set("ctl.start", "uncrypt");
-
- // Connect to the uncrypt service socket.
- LocalSocket socket = connectService();
- if (socket == null) {
- Slog.e(TAG, "Failed to connect to uncrypt socket");
- return false;
- }
-
- // Read the status from the socket.
- DataInputStream dis = null;
- DataOutputStream dos = null;
- try {
- dis = new DataInputStream(socket.getInputStream());
- dos = new DataOutputStream(socket.getOutputStream());
- int lastStatus = Integer.MIN_VALUE;
- while (true) {
- int status = dis.readInt();
- // Avoid flooding the log with the same message.
- if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
- continue;
- }
- lastStatus = status;
-
- if (status >= 0 && status <= 100) {
- // Update status
- Slog.i(TAG, "uncrypt read status: " + status);
- if (listener != null) {
- try {
- listener.onProgress(status);
- } catch (RemoteException ignored) {
- Slog.w(TAG, "RemoteException when posting progress");
- }
- }
- if (status == 100) {
- Slog.i(TAG, "uncrypt successfully finished.");
- // Ack receipt of the final status code. uncrypt
- // waits for the ack so the socket won't be
- // destroyed before we receive the code.
- dos.writeInt(0);
- break;
- }
- } else {
- // Error in /system/bin/uncrypt.
- Slog.e(TAG, "uncrypt failed with status: " + status);
- // Ack receipt of the final status code. uncrypt waits
- // for the ack so the socket won't be destroyed before
- // we receive the code.
- dos.writeInt(0);
- return false;
- }
+ final boolean available = checkAndWaitForUncryptService();
+ if (!available) {
+ Slog.e(TAG, "uncrypt service is unavailable.");
+ return false;
}
- } catch (IOException e) {
- Slog.e(TAG, "IOException when reading status: ", e);
- return false;
- } finally {
- IoUtils.closeQuietly(dis);
- IoUtils.closeQuietly(dos);
- IoUtils.closeQuietly(socket);
- }
- return true;
+ // Write the filename into UNCRYPT_PACKAGE_FILE to be read by
+ // uncrypt.
+ RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
+
+ try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) {
+ uncryptFile.write(filename + "\n");
+ } catch (IOException e) {
+ Slog.e(TAG, "IOException when writing \"" +
+ RecoverySystem.UNCRYPT_PACKAGE_FILE + "\":", e);
+ return false;
+ }
+
+ // Trigger uncrypt via init.
+ SystemProperties.set("ctl.start", "uncrypt");
+
+ // Connect to the uncrypt service socket.
+ LocalSocket socket = connectService();
+ if (socket == null) {
+ Slog.e(TAG, "Failed to connect to uncrypt socket");
+ return false;
+ }
+
+ // Read the status from the socket.
+ DataInputStream dis = null;
+ DataOutputStream dos = null;
+ try {
+ dis = new DataInputStream(socket.getInputStream());
+ dos = new DataOutputStream(socket.getOutputStream());
+ int lastStatus = Integer.MIN_VALUE;
+ while (true) {
+ int status = dis.readInt();
+ // Avoid flooding the log with the same message.
+ if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
+ continue;
+ }
+ lastStatus = status;
+
+ if (status >= 0 && status <= 100) {
+ // Update status
+ Slog.i(TAG, "uncrypt read status: " + status);
+ if (listener != null) {
+ try {
+ listener.onProgress(status);
+ } catch (RemoteException ignored) {
+ Slog.w(TAG, "RemoteException when posting progress");
+ }
+ }
+ if (status == 100) {
+ Slog.i(TAG, "uncrypt successfully finished.");
+ // Ack receipt of the final status code. uncrypt
+ // waits for the ack so the socket won't be
+ // destroyed before we receive the code.
+ dos.writeInt(0);
+ break;
+ }
+ } else {
+ // Error in /system/bin/uncrypt.
+ Slog.e(TAG, "uncrypt failed with status: " + status);
+ // Ack receipt of the final status code. uncrypt waits
+ // for the ack so the socket won't be destroyed before
+ // we receive the code.
+ dos.writeInt(0);
+ return false;
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "IOException when reading status: ", e);
+ return false;
+ } finally {
+ IoUtils.closeQuietly(dis);
+ IoUtils.closeQuietly(dos);
+ IoUtils.closeQuietly(socket);
+ }
+
+ return true;
+ }
}
@Override // Binder call
public boolean clearBcb() {
if (DEBUG) Slog.d(TAG, "clearBcb");
- return setupOrClearBcb(false, null);
+ synchronized (sRequestLock) {
+ return setupOrClearBcb(false, null);
+ }
}
@Override // Binder call
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
- return setupOrClearBcb(true, command);
+ synchronized (sRequestLock) {
+ return setupOrClearBcb(true, command);
+ }
+ }
+
+ @Override // Binder call
+ public void rebootRecoveryWithCommand(String command) {
+ if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
+ synchronized (sRequestLock) {
+ if (!setupOrClearBcb(true, command)) {
+ return;
+ }
+
+ // Having set up the BCB, go ahead and reboot.
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ pm.reboot(PowerManager.REBOOT_RECOVERY);
+ }
+ }
+
+ /**
+ * Check if any of the init services is still running. If so, we cannot
+ * start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
+ * it may break the socket communication since init creates / deletes
+ * the socket (/dev/socket/uncrypt) on service start / exit.
+ */
+ private boolean checkAndWaitForUncryptService() {
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ final String uncryptService = SystemProperties.get(INIT_SERVICE_UNCRYPT);
+ final String setupBcbService = SystemProperties.get(INIT_SERVICE_SETUP_BCB);
+ final String clearBcbService = SystemProperties.get(INIT_SERVICE_CLEAR_BCB);
+ final boolean busy = "running".equals(uncryptService) ||
+ "running".equals(setupBcbService) || "running".equals(clearBcbService);
+ if (DEBUG) {
+ Slog.i(TAG, "retry: " + retry + " busy: " + busy +
+ " uncrypt: [" + uncryptService + "]" +
+ " setupBcb: [" + setupBcbService + "]" +
+ " clearBcb: [" + clearBcbService + "]");
+ }
+
+ if (!busy) {
+ return true;
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted:", e);
+ }
+ }
+
+ return false;
}
private LocalSocket connectService() {
@@ -176,7 +244,7 @@
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
- Slog.w(TAG, "Interrupted: ", e);
+ Slog.w(TAG, "Interrupted:", e);
}
}
}
@@ -190,6 +258,12 @@
private boolean setupOrClearBcb(boolean isSetup, String command) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+ final boolean available = checkAndWaitForUncryptService();
+ if (!available) {
+ Slog.e(TAG, "uncrypt service is unavailable.");
+ return false;
+ }
+
if (isSetup) {
SystemProperties.set("ctl.start", "setup-bcb");
} else {
@@ -232,7 +306,7 @@
return false;
}
} catch (IOException e) {
- Slog.e(TAG, "IOException when communicating with uncrypt: ", e);
+ Slog.e(TAG, "IOException when communicating with uncrypt:", e);
return false;
} finally {
IoUtils.closeQuietly(dis);
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index bb5f62b..2825cf9 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -466,9 +466,8 @@
if (mCarModeEnabled) {
if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
adjustStatusBarCarModeLocked();
-
if (oldAction != null) {
- getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ sendForegroundBroadcastToAllUsers(oldAction);
}
mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
action = UiModeManager.ACTION_ENTER_CAR_MODE;
@@ -476,7 +475,7 @@
} else if (isDeskDockState(mDockState)) {
if (!isDeskDockState(mLastBroadcastState)) {
if (oldAction != null) {
- getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ sendForegroundBroadcastToAllUsers(oldAction);
}
mLastBroadcastState = mDockState;
action = UiModeManager.ACTION_ENTER_DESK_MODE;
@@ -502,6 +501,7 @@
Intent intent = new Intent(action);
intent.putExtra("enableFlags", enableFlags);
intent.putExtra("disableFlags", disableFlags);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
mResultReceiver, null, Activity.RESULT_OK, null, null);
@@ -550,6 +550,11 @@
}
}
+ private void sendForegroundBroadcastToAllUsers(String action) {
+ getContext().sendBroadcastAsUser(new Intent(action)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), UserHandle.ALL);
+ }
+
private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
// Launch a dock activity
String category = null;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
index 63afccc..c3b7e15 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
@@ -142,9 +142,8 @@
final AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
synchronized (accounts.cacheLock) {
- SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- List<Pair<String, Integer>> allAccountGrants = DeDatabaseHelper.findAllAccountGrants(
- db);
+ List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
+ .findAllAccountGrants();
if (allAccountGrants.isEmpty()) {
return null;
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f960d8a..7802576 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -77,7 +77,6 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -99,9 +98,6 @@
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
-import com.android.server.accounts.AccountsDb.CeDatabaseHelper;
-import com.android.server.accounts.AccountsDb.DeDatabaseHelper;
-import com.android.server.accounts.AccountsDb.DebugDbHelper;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
@@ -152,7 +148,7 @@
@Override
public void onStart() {
- mService = new AccountManagerService(getContext());
+ mService = new AccountManagerService(new Injector(getContext()));
publishBinderService(Context.ACCOUNT_SERVICE, mService);
}
@@ -162,13 +158,12 @@
}
}
- private static final int MAX_DEBUG_DB_SIZE = 64;
-
final Context mContext;
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
private UserManager mUserManager;
+ private final Injector mInjector;
final MessageHandler mHandler;
@@ -193,7 +188,7 @@
static class UserAccounts {
private final int userId;
- final DeDatabaseHelper openHelper;
+ final AccountsDb accountsDb;
private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
credentialsPermissionNotificationIds =
new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
@@ -242,12 +237,12 @@
new HashMap<Account, AtomicReference<String>>();
private int debugDbInsertionPoint = -1;
- private SQLiteStatement statementForLogging;
+ private SQLiteStatement statementForLogging; // TODO Move to AccountsDb
UserAccounts(Context context, int userId, File preNDbFile, File deDbFile) {
this.userId = userId;
synchronized (cacheLock) {
- openHelper = DeDatabaseHelper.create(context, userId, preNDbFile, deDbFile);
+ accountsDb = AccountsDb.create(context, userId, preNDbFile, deDbFile);
}
}
}
@@ -272,22 +267,13 @@
return sThis.get();
}
- public AccountManagerService(Context context) {
- this(context, context.getPackageManager(), new AccountAuthenticatorCache(context));
- }
-
- public AccountManagerService(Context context, PackageManager packageManager,
- IAccountAuthenticatorCache authenticatorCache) {
- mContext = context;
- mPackageManager = packageManager;
+ public AccountManagerService(Injector injector) {
+ mInjector = injector;
+ mContext = injector.getContext();
+ mPackageManager = mContext.getPackageManager();
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
-
- ServiceThread serviceThread = new ServiceThread(TAG,
- android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
- serviceThread.start();
- mHandler = new MessageHandler(serviceThread.getLooper());
-
- mAuthenticatorCache = authenticatorCache;
+ mHandler = new MessageHandler(injector.getMessageHandlerLooper());
+ mAuthenticatorCache = mInjector.getAccountAuthenticatorCache();
mAuthenticatorCache.setListener(this, null /* Handler */);
sThis.set(this);
@@ -373,7 +359,7 @@
}
}, UserHandle.ALL, userFilter, null, null);
- LocalServices.addService(AccountManagerInternal.class, new AccountManagerInternalImpl());
+ injector.addLocalService(new AccountManagerInternalImpl());
// Need to cancel account request notifications if the update/install can access the account
new PackageMonitor() {
@@ -424,7 +410,7 @@
final long identity = Binder.clearCallingIdentity();
try {
for (String packageName : packageNames) {
- if (mContext.getPackageManager().checkPermission(
+ if (mPackageManager.checkPermission(
Manifest.permission.GET_ACCOUNTS, packageName)
!= PackageManager.PERMISSION_GRANTED) {
continue;
@@ -710,8 +696,7 @@
* installed on the device.
*/
private void addRequestsForPreInstalledApplications() {
- List<PackageInfo> allInstalledPackages = mContext.getPackageManager().
- getInstalledPackages(0);
+ List<PackageInfo> allInstalledPackages = mPackageManager.getInstalledPackages(0);
for(PackageInfo pi : allInstalledPackages) {
int currentUid = pi.applicationInfo.uid;
if(currentUid != -1) {
@@ -1003,7 +988,7 @@
UserAccounts accounts, boolean invalidateAuthenticatorCache) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "validateAccountsInternal " + accounts.userId
- + " isCeDatabaseAttached=" + accounts.openHelper.isCeDatabaseAttached()
+ + " isCeDatabaseAttached=" + accounts.accountsDb.isCeDatabaseAttached()
+ " userLocked=" + mLocalUnlockedUsers.get(accounts.userId));
}
@@ -1016,11 +1001,11 @@
boolean userUnlocked = isLocalUnlockedUser(accounts.userId);
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
boolean accountDeleted = false;
// Get a map of stored authenticator types to UID
- Map<String, Integer> metaAuthUid = DeDatabaseHelper.findMetaAuthUid(db);
+ final AccountsDb accountsDb = accounts.accountsDb;
+ Map<String, Integer> metaAuthUid = accountsDb.findMetaAuthUid();
// Create a list of authenticator type whose previous uid no longer exists
HashSet<String> obsoleteAuthType = Sets.newHashSet();
SparseBooleanArray knownUids = null;
@@ -1057,7 +1042,7 @@
// So purge its data from the account databases.
obsoleteAuthType.add(type);
// And delete it from the TABLE_META
- DeDatabaseHelper.deleteMetaByAuthTypeAndUid(db, type, uid);
+ accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
}
}
}
@@ -1066,11 +1051,10 @@
// been re-enabled (after being updated for example), then we just overwrite the old
// values.
for (Entry<String, Integer> entry : knownAuth.entrySet()) {
- DeDatabaseHelper.insertOrReplaceMetaAuthTypeAndUid(db, entry.getKey(),
- entry.getValue());
+ accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
}
- final Map<Long, Account> accountsMap = DeDatabaseHelper.findAllAccounts(db);
+ final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
try {
accounts.accountCache.clear();
final HashMap<String, ArrayList<String>> accountNamesByType = new LinkedHashMap<>();
@@ -1080,17 +1064,17 @@
if (obsoleteAuthType.contains(account.type)) {
Slog.w(TAG, "deleting account " + account.name + " because type "
+ account.type + "'s registered authenticator no longer exist.");
- db.beginTransaction();
+ accountsDb.beginTransaction();
try {
- DeDatabaseHelper.deleteAccount(db, accountId);
+ accountsDb.deleteDeAccount(accountId);
// Also delete from CE table if user is unlocked; if user is currently
// locked the account will be removed later by syncDeCeAccountsLocked
if (userUnlocked) {
- AccountsDb.deleteCeAccount(db, accountId);
+ accountsDb.deleteCeAccount(accountId);
}
- db.setTransactionSuccessful();
+ accountsDb.setTransactionSuccessful();
} finally {
- db.endTransaction();
+ accountsDb.endTransaction();
}
accountDeleted = true;
@@ -1170,23 +1154,20 @@
UserAccounts accounts = mUsers.get(userId);
boolean validateAccounts = false;
if (accounts == null) {
- File preNDbFile = new File(getPreNDatabaseName(userId));
- File deDbFile = new File(getDeDatabaseName(userId));
+ File preNDbFile = new File(mInjector.getPreNDatabaseName(userId));
+ File deDbFile = new File(mInjector.getDeDatabaseName(userId));
accounts = new UserAccounts(mContext, userId, preNDbFile, deDbFile);
- initializeDebugDbSizeAndCompileSqlStatementForLogging(
- accounts.openHelper.getWritableDatabase(), accounts);
+ initializeDebugDbSizeAndCompileSqlStatementForLogging(accounts);
mUsers.append(userId, accounts);
purgeOldGrants(accounts);
validateAccounts = true;
}
// open CE database if necessary
- if (!accounts.openHelper.isCeDatabaseAttached() && mLocalUnlockedUsers.get(userId)) {
+ if (!accounts.accountsDb.isCeDatabaseAttached() && mLocalUnlockedUsers.get(userId)) {
Log.i(TAG, "User " + userId + " is unlocked - opening CE database");
synchronized (accounts.cacheLock) {
- File preNDatabaseFile = new File(getPreNDatabaseName(userId));
- File ceDatabaseFile = new File(getCeDatabaseName(userId));
- CeDatabaseHelper.create(mContext, userId, preNDatabaseFile, ceDatabaseFile);
- accounts.openHelper.attachCeDatabase(ceDatabaseFile);
+ File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId));
+ accounts.accountsDb.attachCeDatabase(ceDatabaseFile);
}
syncDeCeAccountsLocked(accounts);
}
@@ -1199,8 +1180,7 @@
private void syncDeCeAccountsLocked(UserAccounts accounts) {
Preconditions.checkState(Thread.holdsLock(mUsers), "mUsers lock must be held");
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
- List<Account> accountsToRemove = AccountsDb.findCeAccountsNotInDe(db);
+ List<Account> accountsToRemove = accounts.accountsDb.findCeAccountsNotInDe();
if (!accountsToRemove.isEmpty()) {
Slog.i(TAG, "Accounts " + accountsToRemove + " were previously deleted while user "
+ accounts.userId + " was locked. Removing accounts from CE tables");
@@ -1223,8 +1203,7 @@
private void purgeOldGrants(UserAccounts accounts) {
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- List<Integer> uids = DeDatabaseHelper.findAllUidGrants(db);
+ List<Integer> uids = accounts.accountsDb.findAllUidGrants();
for (int uid : uids) {
final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
if (packageExists) {
@@ -1232,7 +1211,7 @@
}
Log.d(TAG, "deleting grants for UID " + uid
+ " because its package is no longer installed");
- DeDatabaseHelper.deleteGrantsByUid(db, uid);
+ accounts.accountsDb.deleteGrantsByUid(uid);
}
}
}
@@ -1251,17 +1230,17 @@
}
if (accounts != null) {
synchronized (accounts.cacheLock) {
- accounts.openHelper.close();
+ accounts.accountsDb.close();
}
}
Log.i(TAG, "Removing database files for user " + userId);
- File dbFile = new File(getDeDatabaseName(userId));
+ File dbFile = new File(mInjector.getDeDatabaseName(userId));
AccountsDb.deleteDbFileWarnIfFailed(dbFile);
// Remove CE file if user is unlocked, or FBE is not enabled
boolean fbeEnabled = StorageManager.isFileEncryptedNativeOrEmulated();
if (!fbeEnabled || userUnlocked) {
- File ceDb = new File(getCeDatabaseName(userId));
+ File ceDb = new File(mInjector.getCeDatabaseName(userId));
if (ceDb.exists()) {
AccountsDb.deleteDbFileWarnIfFailed(ceDb);
}
@@ -1344,9 +1323,7 @@
}
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
- return CeDatabaseHelper.findAccountPasswordByNameAndType(db, account.name,
- account.type);
+ return accounts.accountsDb.findAccountPasswordByNameAndType(account.name, account.type);
}
}
@@ -1375,8 +1352,7 @@
synchronized (accounts.cacheLock) {
AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
if (previousNameRef == null) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- String previousName = DeDatabaseHelper.findAccountPreviousName(db, account);
+ String previousName = accounts.accountsDb.findDeAccountPreviousName(account);
previousNameRef = new AtomicReference<>(previousName);
accounts.previousNameCache.put(account, previousNameRef);
return previousName;
@@ -1612,8 +1588,7 @@
private boolean updateLastAuthenticatedTime(Account account) {
final UserAccounts accounts = getUserAccountsForCaller();
synchronized (accounts.cacheLock) {
- return DeDatabaseHelper.updateAccountLastAuthenticatedTime(
- accounts.openHelper.getWritableDatabase(), account);
+ return accounts.accountsDb.updateAccountLastAuthenticatedTime(account);
}
}
@@ -1682,22 +1657,21 @@
return false;
}
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
try {
- if (CeDatabaseHelper.findAccountId(db, account) >= 0) {
+ if (accounts.accountsDb.findCeAccountId(account) >= 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping since the account already exists");
return false;
}
- long accountId = CeDatabaseHelper.insertAccount(db, account, password);
+ long accountId = accounts.accountsDb.insertCeAccount(account, password);
if (accountId < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping the DB insert failed");
return false;
}
// Insert into DE table
- if (DeDatabaseHelper.insertAccount(db, account, accountId) < 0) {
+ if (accounts.accountsDb.insertDeAccount(account, accountId) < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping the DB insert failed");
return false;
@@ -1705,21 +1679,21 @@
if (extras != null) {
for (String key : extras.keySet()) {
final String value = extras.getString(key);
- if (CeDatabaseHelper.insertExtra(db, accountId, key, value) < 0) {
+ if (accounts.accountsDb.insertExtra(accountId, key, value) < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping since insertExtra failed for key " + key);
return false;
}
}
}
- db.setTransactionSuccessful();
+ accounts.accountsDb.setTransactionSuccessful();
logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
accountId, accounts, callingUid);
insertAccountIntoCacheLocked(accounts, account);
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
}
if (getUserManager().getUserInfo(accounts.userId).canHaveProfile()) {
@@ -1902,17 +1876,16 @@
}
}
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
Account renamedAccount = new Account(newName, accountToRename.type);
try {
- final long accountId = DeDatabaseHelper.findAccountId(db, accountToRename);
+ final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
if (accountId >= 0) {
- CeDatabaseHelper.renameAccount(db, accountId, newName);
- DeDatabaseHelper.renameAccount(db, accountId, newName, accountToRename.name);
+ accounts.accountsDb.renameCeAccount(accountId, newName);
+ accounts.accountsDb.renameDeAccount(accountId, newName, accountToRename.name);
}
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
/*
* Database transaction was successful. Clean up cached
@@ -2035,8 +2008,7 @@
}
}
}
- SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- final long accountId = DeDatabaseHelper.findAccountId(db, account);
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
logRecord(
AccountsDb.DEBUG_ACTION_CALLED_ACCOUNT_REMOVE,
AccountsDb.TABLE_ACCOUNTS,
@@ -2075,8 +2047,7 @@
}
removeVisibleListFunctionality(account, getUserAccounts(UserHandle.getUserId(callingUid)));
UserAccounts accounts = getUserAccountsForCaller();
- SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- final long accountId = DeDatabaseHelper.findAccountId(db, account);
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
logRecord(
AccountsDb.DEBUG_ACTION_CALLED_ACCOUNT_REMOVE,
AccountsDb.TABLE_ACCOUNTS,
@@ -2153,26 +2124,23 @@
+ " is still locked. CE data will be removed later");
}
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = userUnlocked
- ? accounts.openHelper.getWritableDatabaseUserIsUnlocked()
- : accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
// Set to a dummy value, this will only be used if the database
// transaction succeeds.
long accountId = -1;
try {
- accountId = DeDatabaseHelper.findAccountId(db, account);
+ accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId >= 0) {
- DeDatabaseHelper.deleteAccount(db, accountId);
+ accounts.accountsDb.deleteDeAccount(accountId);
if (userUnlocked) {
// Delete from CE table
- AccountsDb.deleteCeAccount(db, accountId);
+ accounts.accountsDb.deleteCeAccount(accountId);
}
- db.setTransactionSuccessful();
+ accounts.accountsDb.setTransactionSuccessful();
isChanged = true;
}
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
if (isChanged) {
removeAccountFromCacheLocked(accounts, account);
@@ -2231,14 +2199,13 @@
try {
UserAccounts accounts = getUserAccounts(userId);
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
try {
- invalidateAuthTokenLocked(accounts, db, accountType, authToken);
+ invalidateAuthTokenLocked(accounts, accountType, authToken);
invalidateCustomTokenLocked(accounts, accountType, authToken);
- db.setTransactionSuccessful();
+ accounts.accountsDb.setTransactionSuccessful();
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
}
} finally {
@@ -2257,21 +2224,20 @@
accounts.accountTokenCaches.remove(accountType, authToken);
}
- private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
- String accountType, String authToken) {
+ private void invalidateAuthTokenLocked(UserAccounts accounts, String accountType,
+ String authToken) {
if (authToken == null || accountType == null) {
return;
}
- Cursor cursor = CeDatabaseHelper.findAuthtokenForAllAccounts(db, accountType, authToken);
+ Cursor cursor = accounts.accountsDb.findAuthtokenForAllAccounts(accountType, authToken);
try {
while (cursor.moveToNext()) {
String authTokenId = cursor.getString(0);
String accountName = cursor.getString(1);
String authTokenType = cursor.getString(2);
- CeDatabaseHelper.deleteAuthToken(db, authTokenId);
+ accounts.accountsDb.deleteAuthToken(authTokenId);
writeAuthTokenIntoCacheLocked(
accounts,
- db,
new Account(accountName, accountType),
authTokenType,
null);
@@ -2309,22 +2275,21 @@
cancelNotification(getSigninRequiredNotificationId(accounts, account),
UserHandle.of(accounts.userId));
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
try {
- long accountId = DeDatabaseHelper.findAccountId(db, account);
+ long accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId < 0) {
return false;
}
- CeDatabaseHelper.deleteAuthtokensByAccountIdAndType(db, accountId, type);
- if (CeDatabaseHelper.insertAuthToken(db, accountId, type, authToken) >= 0) {
- db.setTransactionSuccessful();
- writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
+ accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
+ if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
+ accounts.accountsDb.setTransactionSuccessful();
+ writeAuthTokenIntoCacheLocked(accounts, account, type, authToken);
return true;
}
return false;
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
}
}
@@ -2423,16 +2388,15 @@
}
boolean isChanged = false;
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
try {
- final long accountId = DeDatabaseHelper.findAccountId(db, account);
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId >= 0) {
- CeDatabaseHelper.updateAccountPassword(db, accountId, password);
- CeDatabaseHelper.deleteAuthTokensByAccountId(db, accountId);
+ accounts.accountsDb.updateCeAccountPassword(accountId, password);
+ accounts.accountsDb.deleteAuthTokensByAccountId(accountId);
accounts.authTokenCache.remove(account);
accounts.accountTokenCaches.remove(account);
- db.setTransactionSuccessful();
+ accounts.accountsDb.setTransactionSuccessful();
// If there is an account whose password will be updated and the database
// transactions succeed, then we say that a change has occured. Even if the
// new password is the same as the old and there were no authtokens to delete.
@@ -2443,7 +2407,7 @@
logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts, callingUid);
}
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
if (isChanged) {
// Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
sendAccountsChangedBroadcast(accounts.userId);
@@ -2533,26 +2497,25 @@
if (account == null || key == null) {
return;
}
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
try {
- long accountId = DeDatabaseHelper.findAccountId(db, account);
+ long accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId < 0) {
return;
}
- long extrasId = CeDatabaseHelper.findExtrasIdByAccountId(db, accountId, key);
+ long extrasId = accounts.accountsDb.findExtrasIdByAccountId(accountId, key);
if (extrasId < 0) {
- extrasId = CeDatabaseHelper.insertExtra(db, accountId, key, value);
+ extrasId = accounts.accountsDb.insertExtra(accountId, key, value);
if (extrasId < 0) {
return;
}
- } else if (!CeDatabaseHelper.updateExtra(db, extrasId, value)) {
+ } else if (!accounts.accountsDb.updateExtra(extrasId, value)) {
return;
}
- writeUserDataIntoCacheLocked(accounts, db, account, key, value);
- db.setTransactionSuccessful();
+ writeUserDataIntoCacheLocked(accounts, account, key, value);
+ accounts.accountsDb.setTransactionSuccessful();
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
}
@@ -4181,9 +4144,8 @@
private boolean addSharedAccountAsUser(Account account, int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
- SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- DeDatabaseHelper.deleteSharedAccount(db, account);
- long accountId = DeDatabaseHelper.insertSharedAccount(db, account);
+ accounts.accountsDb.deleteSharedAccount(account);
+ long accountId = accounts.accountsDb.insertSharedAccount(account);
if (accountId < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping the DB insert failed");
@@ -4198,9 +4160,8 @@
public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
- SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- long sharedTableAccountId = DeDatabaseHelper.findSharedAccountId(db, account);
- int r = DeDatabaseHelper.renameSharedAccount(db, account, newName);
+ long sharedTableAccountId = accounts.accountsDb.findSharedAccountId(account);
+ int r = accounts.accountsDb.renameSharedAccount(account, newName);
if (r > 0) {
int callingUid = getCallingUid();
logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_RENAME, AccountsDb.TABLE_SHARED_ACCOUNTS,
@@ -4219,9 +4180,8 @@
private boolean removeSharedAccountAsUser(Account account, int userId, int callingUid) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
- SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- long sharedTableAccountId = DeDatabaseHelper.findSharedAccountId(db, account);
- boolean deleted = DeDatabaseHelper.deleteSharedAccount(db, account);
+ long sharedTableAccountId = accounts.accountsDb.findSharedAccountId(account);
+ boolean deleted = accounts.accountsDb.deleteSharedAccount(account);
if (deleted) {
logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE, AccountsDb.TABLE_SHARED_ACCOUNTS,
sharedTableAccountId, accounts, callingUid);
@@ -4233,8 +4193,8 @@
@Override
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
- SQLiteDatabase db = getUserAccounts(userId).openHelper.getReadableDatabase();
- List<Account> accountList = DeDatabaseHelper.getSharedAccounts(db);
+ UserAccounts accounts = getUserAccounts(userId);
+ List<Account> accountList = accounts.accountsDb.getSharedAccounts();
Account[] accountArray = new Account[accountList.size()];
accountList.toArray(accountArray);
return accountArray;
@@ -4585,9 +4545,8 @@
if (mAuthDetailsRequired) {
long lastAuthenticatedTime = -1;
if (accountPresent) {
- lastAuthenticatedTime = DeDatabaseHelper
+ lastAuthenticatedTime = mAccounts.accountsDb
.findAccountLastAuthenticatedTime(
- mAccounts.openHelper.getReadableDatabase(),
new Account(mAccountName, mAccountType));
}
result.putLong(AccountManager.KEY_LAST_AUTHENTICATED_TIME,
@@ -4745,47 +4704,6 @@
}
}
- @VisibleForTesting
- String getPreNDatabaseName(int userId) {
- File systemDir = Environment.getDataSystemDirectory();
- File databaseFile = new File(Environment.getUserSystemDirectory(userId),
- PRE_N_DATABASE_NAME);
- if (userId == 0) {
- // Migrate old file, if it exists, to the new location.
- // Make sure the new file doesn't already exist. A dummy file could have been
- // accidentally created in the old location, causing the new one to become corrupted
- // as well.
- File oldFile = new File(systemDir, PRE_N_DATABASE_NAME);
- if (oldFile.exists() && !databaseFile.exists()) {
- // Check for use directory; create if it doesn't exist, else renameTo will fail
- File userDir = Environment.getUserSystemDirectory(userId);
- if (!userDir.exists()) {
- if (!userDir.mkdirs()) {
- throw new IllegalStateException("User dir cannot be created: " + userDir);
- }
- }
- if (!oldFile.renameTo(databaseFile)) {
- throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
- }
- }
- }
- return databaseFile.getPath();
- }
-
- @VisibleForTesting
- String getDeDatabaseName(int userId) {
- File databaseFile = new File(Environment.getDataSystemDeDirectory(userId),
- AccountsDb.DE_DATABASE_NAME);
- return databaseFile.getPath();
- }
-
- @VisibleForTesting
- String getCeDatabaseName(int userId) {
- File databaseFile = new File(Environment.getDataSystemCeDirectory(userId),
- AccountsDb.CE_DATABASE_NAME);
- return databaseFile.getPath();
- }
-
private void logRecord(UserAccounts accounts, String action, String tableName) {
logRecord(action, tableName, -1, accounts);
}
@@ -4846,7 +4764,7 @@
LogRecordTask logTask = new LogRecordTask(action, tableName, accountId, userAccount,
callingUid, userAccount.debugDbInsertionPoint);
userAccount.debugDbInsertionPoint = (userAccount.debugDbInsertionPoint + 1)
- % MAX_DEBUG_DB_SIZE;
+ % AccountsDb.MAX_DEBUG_DB_SIZE;
mHandler.post(logTask);
}
@@ -4854,17 +4772,10 @@
* This should only be called once to compile the sql statement for logging
* and to find the insertion point.
*/
- private void initializeDebugDbSizeAndCompileSqlStatementForLogging(SQLiteDatabase db,
- UserAccounts userAccount) {
- // Initialize the count if not done earlier.
- int size = DebugDbHelper.getDebugTableRowCount(db);
- if (size >= MAX_DEBUG_DB_SIZE) {
- // Table is full, and we need to find the point where to insert.
- userAccount.debugDbInsertionPoint = DebugDbHelper.getDebugTableInsertionPoint(db);
- } else {
- userAccount.debugDbInsertionPoint = size;
- }
- userAccount.statementForLogging = DebugDbHelper.compileSqlStatementForLogging(db);
+ private void initializeDebugDbSizeAndCompileSqlStatementForLogging(UserAccounts userAccount) {
+ userAccount.debugDbInsertionPoint = userAccount.accountsDb
+ .calculateDebugTableInsertionPoint();
+ userAccount.statementForLogging = userAccount.accountsDb.compileSqlStatementForLogging();
}
public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
@@ -4913,11 +4824,9 @@
private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
String[] args, boolean isCheckinRequest) {
synchronized (userAccounts.cacheLock) {
- final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase();
-
if (isCheckinRequest) {
// This is a checkin request. *Only* upload the account types and the count of each.
- DeDatabaseHelper.dumpAccountsTable(db, fout);
+ userAccounts.accountsDb.dumpDeAccountsTable(fout);
} else {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
Process.myUid(), null);
@@ -4928,7 +4837,7 @@
// Add debug information.
fout.println();
- DebugDbHelper.dumpDebugTable(db, fout);
+ userAccounts.accountsDb.dumpDebugTable(fout);
fout.println();
synchronized (mSessions) {
final long now = SystemClock.elapsedRealtime();
@@ -4981,17 +4890,11 @@
}
}
- @VisibleForTesting
- protected void installNotification(int notificationId, final Notification notification,
- UserHandle user) {
- installNotification(notificationId, notification, "android", user.getIdentifier());
- }
-
private void installNotification(int notificationId, final Notification notification,
String packageName, int userId) {
final long token = clearCallingIdentity();
try {
- INotificationManager notificationManager = NotificationManager.getService();
+ INotificationManager notificationManager = mInjector.getNotificationManager();
try {
notificationManager.enqueueNotificationWithTag(packageName, packageName, null,
notificationId, notification, new int[1], userId);
@@ -5003,16 +4906,14 @@
}
}
- @VisibleForTesting
- protected void cancelNotification(int id, UserHandle user) {
+ private void cancelNotification(int id, UserHandle user) {
cancelNotification(id, mContext.getPackageName(), user);
}
- protected void cancelNotification(int id, String packageName, UserHandle user) {
+ private void cancelNotification(int id, String packageName, UserHandle user) {
long identityToken = clearCallingIdentity();
try {
- INotificationManager service = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ INotificationManager service = mInjector.getNotificationManager();
service.cancelNotificationWithTag(packageName, null, id, user.getIdentifier());
} catch (RemoteException e) {
/* ignore - local call */
@@ -5198,13 +5099,12 @@
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callerUid));
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
long grantsCount;
if (authTokenType != null) {
- grantsCount = DeDatabaseHelper.findMatchingGrantsCount(db, callerUid, authTokenType,
+ grantsCount = accounts.accountsDb.findMatchingGrantsCount(callerUid, authTokenType,
account);
} else {
- grantsCount = DeDatabaseHelper.findMatchingGrantsCountAnyToken(db, callerUid,
+ grantsCount = accounts.accountsDb.findMatchingGrantsCountAnyToken(callerUid,
account);
}
final boolean permissionGranted = grantsCount > 0;
@@ -5332,10 +5232,9 @@
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- long accountId = DeDatabaseHelper.findAccountId(db, account);
+ long accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId >= 0) {
- DeDatabaseHelper.insertGrant(db, accountId, authTokenType, uid);
+ accounts.accountsDb.insertGrant(accountId, authTokenType, uid);
}
cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
UserHandle.of(accounts.userId));
@@ -5365,17 +5264,16 @@
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
+ accounts.accountsDb.beginTransaction();
try {
- long accountId = DeDatabaseHelper.findAccountId(db, account);
+ long accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId >= 0) {
- DeDatabaseHelper.deleteGrantsByAccountIdAuthTokenTypeAndUid(
- db, accountId, authTokenType, uid);
- db.setTransactionSuccessful();
+ accounts.accountsDb.deleteGrantsByAccountIdAuthTokenTypeAndUid(
+ accountId, authTokenType, uid);
+ accounts.accountsDb.setTransactionSuccessful();
}
} finally {
- db.endTransaction();
+ accounts.accountsDb.endTransaction();
}
cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
@@ -5531,11 +5429,11 @@
}
}
- protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+ protected void writeUserDataIntoCacheLocked(UserAccounts accounts,
Account account, String key, String value) {
Map<String, String> userDataForAccount = accounts.userDataCache.get(account);
if (userDataForAccount == null) {
- userDataForAccount = CeDatabaseHelper.findUserExtrasForAccount(db, account);
+ userDataForAccount = accounts.accountsDb.findUserExtrasForAccount(account);
accounts.userDataCache.put(account, userDataForAccount);
}
if (value == null) {
@@ -5557,11 +5455,11 @@
}
}
- protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+ protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts,
Account account, String key, String value) {
Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
if (authTokensForAccount == null) {
- authTokensForAccount = CeDatabaseHelper.findAuthTokensByAccount(db, account);
+ authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
accounts.authTokenCache.put(account, authTokensForAccount);
}
if (value == null) {
@@ -5577,8 +5475,7 @@
Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
if (authTokensForAccount == null) {
// need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
- authTokensForAccount = CeDatabaseHelper.findAuthTokensByAccount(db, account);
+ authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
accounts.authTokenCache.put(account, authTokensForAccount);
}
return authTokensForAccount.get(authTokenType);
@@ -5590,8 +5487,7 @@
Map<String, String> userDataForAccount = accounts.userDataCache.get(account);
if (userDataForAccount == null) {
// need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
- userDataForAccount = CeDatabaseHelper.findUserExtrasForAccount(db, account);
+ userDataForAccount = accounts.accountsDb.findUserExtrasForAccount(account);
accounts.userDataCache.put(account, userDataForAccount);
}
return userDataForAccount.get(key);
@@ -5714,4 +5610,74 @@
}
}
}
+
+ @VisibleForTesting
+ static class Injector {
+ private final Context mContext;
+
+ public Injector(Context context) {
+ mContext = context;
+ }
+
+ Looper getMessageHandlerLooper() {
+ ServiceThread serviceThread = new ServiceThread(TAG,
+ android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ serviceThread.start();
+ return serviceThread.getLooper();
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ void addLocalService(AccountManagerInternal service) {
+ LocalServices.addService(AccountManagerInternal.class, service);
+ }
+
+ String getDeDatabaseName(int userId) {
+ File databaseFile = new File(Environment.getDataSystemDeDirectory(userId),
+ AccountsDb.DE_DATABASE_NAME);
+ return databaseFile.getPath();
+ }
+
+ String getCeDatabaseName(int userId) {
+ File databaseFile = new File(Environment.getDataSystemCeDirectory(userId),
+ AccountsDb.CE_DATABASE_NAME);
+ return databaseFile.getPath();
+ }
+
+ String getPreNDatabaseName(int userId) {
+ File systemDir = Environment.getDataSystemDirectory();
+ File databaseFile = new File(Environment.getUserSystemDirectory(userId),
+ PRE_N_DATABASE_NAME);
+ if (userId == 0) {
+ // Migrate old file, if it exists, to the new location.
+ // Make sure the new file doesn't already exist. A dummy file could have been
+ // accidentally created in the old location, causing the new one to become corrupted
+ // as well.
+ File oldFile = new File(systemDir, PRE_N_DATABASE_NAME);
+ if (oldFile.exists() && !databaseFile.exists()) {
+ // Check for use directory; create if it doesn't exist, else renameTo will fail
+ File userDir = Environment.getUserSystemDirectory(userId);
+ if (!userDir.exists()) {
+ if (!userDir.mkdirs()) {
+ throw new IllegalStateException("User dir cannot be created: " + userDir);
+ }
+ }
+ if (!oldFile.renameTo(databaseFile)) {
+ throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
+ }
+ }
+ }
+ return databaseFile.getPath();
+ }
+
+ IAccountAuthenticatorCache getAccountAuthenticatorCache() {
+ return new AccountAuthenticatorCache(mContext);
+ }
+
+ INotificationManager getNotificationManager() {
+ return NotificationManager.getService();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index 6ef521e..cb594f6 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -42,8 +42,13 @@
/**
* Persistence layer abstraction for accessing accounts_ce/accounts_de databases.
+ *
+ * <p>At first, CE database needs to be {@link #attachCeDatabase(File) attached to DE},
+ * in order for the tables to be available. All operations with CE database are done through the
+ * connection to the DE database, to which it is attached. This approach allows atomic
+ * transactions across two databases</p>
*/
-class AccountsDb {
+class AccountsDb implements AutoCloseable {
private static final String TAG = "AccountsDb";
private static final String DATABASE_NAME = "accounts.db";
@@ -128,6 +133,8 @@
private static final String CE_TABLE_AUTHTOKENS = CE_DB_PREFIX + TABLE_AUTHTOKENS;
private static final String CE_TABLE_EXTRAS = CE_DB_PREFIX + TABLE_EXTRAS;
+ static final int MAX_DEBUG_DB_SIZE = 64;
+
private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
@@ -169,7 +176,17 @@
private static final String META_KEY_DELIMITER = ":";
private static final String SELECTION_META_BY_AUTHENTICATOR_TYPE = META_KEY + " LIKE ?";
- static class CeDatabaseHelper extends SQLiteOpenHelper {
+ private final DeDatabaseHelper mDeDatabase;
+ private final Context mContext;
+ private final File mPreNDatabaseFile;
+
+ AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile) {
+ mDeDatabase = deDatabase;
+ mContext = context;
+ mPreNDatabaseFile = preNDatabaseFile;
+ }
+
+ private static class CeDatabaseHelper extends SQLiteOpenHelper {
CeDatabaseHelper(Context context, String ceDatabaseName) {
super(context, ceDatabaseName, null, CE_DATABASE_VERSION);
@@ -249,17 +266,16 @@
/**
* Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location,
* it also performs migration to the new CE database.
- * @param userId id of the user where the database is located
*/
static CeDatabaseHelper create(
Context context,
- int userId,
File preNDatabaseFile,
File ceDatabaseFile) {
boolean newDbExists = ceDatabaseFile.exists();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "CeDatabaseHelper.create userId=" + userId + " oldDbExists="
- + preNDatabaseFile.exists() + " newDbExists=" + newDbExists);
+ Log.v(TAG, "CeDatabaseHelper.create ceDatabaseFile=" + ceDatabaseFile
+ + " oldDbExists=" + preNDatabaseFile.exists()
+ + " newDbExists=" + newDbExists);
}
boolean removeOldDb = false;
if (!newDbExists && preNDatabaseFile.exists()) {
@@ -290,187 +306,189 @@
}
return true;
}
-
- /**
- * Returns information about auth tokens and their account for the specified query
- * parameters.
- * Output is in the format:
- * <pre><code> | AUTHTOKEN_ID | ACCOUNT_NAME | AUTH_TOKEN_TYPE |</code></pre>
- */
- static Cursor findAuthtokenForAllAccounts(SQLiteDatabase db, String accountType,
- String authToken) {
- return db.rawQuery(
- "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
- + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
- + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
- + " FROM " + CE_TABLE_ACCOUNTS
- + " JOIN " + CE_TABLE_AUTHTOKENS
- + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
- + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID
- + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN
- + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
- new String[]{authToken, accountType});
- }
-
- static boolean deleteAuthtokensByAccountIdAndType(SQLiteDatabase db, long accountId,
- String authtokenType) {
- return db.delete(CE_TABLE_AUTHTOKENS,
- AUTHTOKENS_ACCOUNTS_ID + "=?" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
- new String[]{String.valueOf(accountId), authtokenType}) > 0;
- }
-
- static boolean deleteAuthToken(SQLiteDatabase db, String authTokenId) {
- return db.delete(
- CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "= ?",
- new String[]{authTokenId}) > 0;
- }
-
- static long insertAuthToken(SQLiteDatabase db, long accountId, String authTokenType,
- String authToken) {
- ContentValues values = new ContentValues();
- values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
- values.put(AUTHTOKENS_TYPE, authTokenType);
- values.put(AUTHTOKENS_AUTHTOKEN, authToken);
- return db.insert(
- CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values);
- }
-
- static Map<String, String> findAuthTokensByAccount(final SQLiteDatabase db,
- Account account) {
- HashMap<String, String> authTokensForAccount = new HashMap<>();
- Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
- COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
- SELECTION_AUTHTOKENS_BY_ACCOUNT,
- new String[]{account.name, account.type},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- final String type = cursor.getString(0);
- final String authToken = cursor.getString(1);
- authTokensForAccount.put(type, authToken);
- }
- } finally {
- cursor.close();
- }
- return authTokensForAccount;
- }
-
- static int updateAccountPassword(SQLiteDatabase db, long accountId, String password) {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_PASSWORD, password);
- return db.update(
- CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?",
- new String[]{String.valueOf(accountId)});
- }
-
- static boolean renameAccount(SQLiteDatabase db, long accountId, String newName) {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, newName);
- final String[] argsAccountId = {String.valueOf(accountId)};
- return db.update(
- CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
- }
-
- static boolean deleteAuthTokensByAccountId(SQLiteDatabase db, long accountId) {
- return db.delete(
- CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?",
- new String[]{String.valueOf(accountId)}) > 0;
- }
-
- static long findExtrasIdByAccountId(SQLiteDatabase db, long accountId, String key) {
- Cursor cursor = db.query(
- CE_TABLE_EXTRAS, new String[]{EXTRAS_ID},
- EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
- new String[]{key}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- static boolean updateExtra(SQLiteDatabase db, long extrasId, String value) {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_VALUE, value);
- int rows = db.update(
- TABLE_EXTRAS, values, EXTRAS_ID + "=?",
- new String[]{String.valueOf(extrasId)});
- return rows == 1;
- }
-
- static long insertExtra(SQLiteDatabase db, long accountId, String key, String value) {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_KEY, key);
- values.put(EXTRAS_ACCOUNTS_ID, accountId);
- values.put(EXTRAS_VALUE, value);
- return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values);
- }
-
- static Map<String, String> findUserExtrasForAccount(SQLiteDatabase db, Account account) {
- Map<String, String> userExtrasForAccount = new HashMap<>();
- Cursor cursor = db.query(CE_TABLE_EXTRAS,
- COLUMNS_EXTRAS_KEY_AND_VALUE,
- SELECTION_USERDATA_BY_ACCOUNT,
- new String[]{account.name, account.type},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- final String tmpkey = cursor.getString(0);
- final String value = cursor.getString(1);
- userExtrasForAccount.put(tmpkey, value);
- }
- } finally {
- cursor.close();
- }
- return userExtrasForAccount;
- }
-
- static long findAccountId(SQLiteDatabase db, Account account) {
- Cursor cursor = db.query(
- CE_TABLE_ACCOUNTS, new String[]{
- ACCOUNTS_ID},
- "name=? AND type=?", new String[]{account.name, account.type}, null, null,
- null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- static String findAccountPasswordByNameAndType(SQLiteDatabase db, String name,
- String type) {
- Cursor cursor = db.query(CE_TABLE_ACCOUNTS, new String[]{
- ACCOUNTS_PASSWORD},
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
- new String[]{name, type}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getString(0);
- }
- return null;
- } finally {
- cursor.close();
- }
- }
-
- static long insertAccount(SQLiteDatabase db, Account account, String password) {
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, account.name);
- values.put(ACCOUNTS_TYPE, account.type);
- values.put(ACCOUNTS_PASSWORD, password);
- return db.insert(
- CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
- }
-
}
+ /**
+ * Returns information about auth tokens and their account for the specified query
+ * parameters.
+ * Output is in the format:
+ * <pre><code> | AUTHTOKEN_ID | ACCOUNT_NAME | AUTH_TOKEN_TYPE |</code></pre>
+ */
+ Cursor findAuthtokenForAllAccounts(String accountType, String authToken) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
+ return db.rawQuery(
+ "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
+ + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+ + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
+ + " FROM " + CE_TABLE_ACCOUNTS
+ + " JOIN " + CE_TABLE_AUTHTOKENS
+ + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+ + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID
+ + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN
+ + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
+ new String[]{authToken, accountType});
+ }
+
+ Map<String, String> findAuthTokensByAccount(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
+ HashMap<String, String> authTokensForAccount = new HashMap<>();
+ Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
+ COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
+ SELECTION_AUTHTOKENS_BY_ACCOUNT,
+ new String[] {account.name, account.type},
+ null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final String type = cursor.getString(0);
+ final String authToken = cursor.getString(1);
+ authTokensForAccount.put(type, authToken);
+ }
+ } finally {
+ cursor.close();
+ }
+ return authTokensForAccount;
+ }
+
+ boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ return db.delete(CE_TABLE_AUTHTOKENS,
+ AUTHTOKENS_ACCOUNTS_ID + "=?" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
+ new String[]{String.valueOf(accountId), authtokenType}) > 0;
+ }
+
+ boolean deleteAuthToken(String authTokenId) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ return db.delete(
+ CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "= ?",
+ new String[]{authTokenId}) > 0;
+ }
+
+ long insertAuthToken(long accountId, String authTokenType, String authToken) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ ContentValues values = new ContentValues();
+ values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
+ values.put(AUTHTOKENS_TYPE, authTokenType);
+ values.put(AUTHTOKENS_AUTHTOKEN, authToken);
+ return db.insert(
+ CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values);
+ }
+
+ int updateCeAccountPassword(long accountId, String password) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_PASSWORD, password);
+ return db.update(
+ CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?",
+ new String[] {String.valueOf(accountId)});
+ }
+
+ boolean renameCeAccount(long accountId, String newName) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, newName);
+ final String[] argsAccountId = {String.valueOf(accountId)};
+ return db.update(
+ CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
+ }
+
+ boolean deleteAuthTokensByAccountId(long accountId) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ return db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?",
+ new String[] {String.valueOf(accountId)}) > 0;
+ }
+
+ long findExtrasIdByAccountId(long accountId, String key) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
+ Cursor cursor = db.query(
+ CE_TABLE_EXTRAS, new String[]{EXTRAS_ID},
+ EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
+ new String[]{key}, null, null, null);
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getLong(0);
+ }
+ return -1;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ boolean updateExtra(long extrasId, String value) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_VALUE, value);
+ int rows = db.update(
+ TABLE_EXTRAS, values, EXTRAS_ID + "=?",
+ new String[]{String.valueOf(extrasId)});
+ return rows == 1;
+ }
+
+ long insertExtra(long accountId, String key, String value) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_KEY, key);
+ values.put(EXTRAS_ACCOUNTS_ID, accountId);
+ values.put(EXTRAS_VALUE, value);
+ return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values);
+ }
+
+ Map<String, String> findUserExtrasForAccount(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
+ Map<String, String> userExtrasForAccount = new HashMap<>();
+ String[] selectionArgs = {account.name, account.type};
+ try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
+ COLUMNS_EXTRAS_KEY_AND_VALUE,
+ SELECTION_USERDATA_BY_ACCOUNT,
+ selectionArgs,
+ null, null, null)) {
+ while (cursor.moveToNext()) {
+ final String tmpkey = cursor.getString(0);
+ final String value = cursor.getString(1);
+ userExtrasForAccount.put(tmpkey, value);
+ }
+ }
+ return userExtrasForAccount;
+ }
+
+ long findCeAccountId(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
+ String[] columns = { ACCOUNTS_ID };
+ String selection = "name=? AND type=?";
+ String[] selectionArgs = {account.name, account.type};
+ try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
+ null, null, null)) {
+ if (cursor.moveToNext()) {
+ return cursor.getLong(0);
+ }
+ return -1;
+ }
+ }
+
+ String findAccountPasswordByNameAndType(String name, String type) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
+ String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
+ String[] selectionArgs = {name, type};
+ String[] columns = {ACCOUNTS_PASSWORD};
+ try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
+ null, null, null)) {
+ if (cursor.moveToNext()) {
+ return cursor.getString(0);
+ }
+ return null;
+ }
+ }
+
+ long insertCeAccount(Account account, String password) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
+ ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, account.name);
+ values.put(ACCOUNTS_TYPE, account.type);
+ values.put(ACCOUNTS_PASSWORD, password);
+ return db.insert(
+ CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
+ }
+
+
static class DeDatabaseHelper extends SQLiteOpenHelper {
private final int mUserId;
@@ -504,7 +522,7 @@
createGrantsTable(db);
createSharedAccountsTable(db);
createAccountsDeletionTrigger(db);
- DebugDbHelper.createDebugTable(db);
+ createDebugTable(db);
}
private void createSharedAccountsTable(SQLiteDatabase db) {
@@ -533,6 +551,18 @@
+ "," + GRANTS_GRANTEE_UID + "))");
}
+ static void createDebugTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
+ + ACCOUNTS_ID + " INTEGER,"
+ + DEBUG_TABLE_ACTION_TYPE + " TEXT NOT NULL, "
+ + DEBUG_TABLE_TIMESTAMP + " DATETIME,"
+ + DEBUG_TABLE_CALLER_UID + " INTEGER NOT NULL,"
+ + DEBUG_TABLE_TABLE_NAME + " TEXT NOT NULL,"
+ + DEBUG_TABLE_KEY + " INTEGER PRIMARY KEY)");
+ db.execSQL("CREATE INDEX timestamp_index ON " + TABLE_DEBUG + " ("
+ + DEBUG_TABLE_TIMESTAMP + ")");
+ }
+
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
@@ -542,17 +572,6 @@
}
}
- public void attachCeDatabase(File ceDbFile) {
- SQLiteDatabase db = getWritableDatabase();
- db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb");
- mCeAttached = true;
- }
-
- public boolean isCeDatabaseAttached() {
- return mCeAttached;
- }
-
-
public SQLiteDatabase getReadableDatabaseUserIsUnlocked() {
if(!mCeAttached) {
Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + mUserId
@@ -616,343 +635,308 @@
db.execSQL("DETACH DATABASE preNDb");
}
+ }
- static boolean deleteAccount(SQLiteDatabase db, long accountId) {
- return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
- }
+ boolean deleteDeAccount(long accountId) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
+ }
- static long insertSharedAccount(SQLiteDatabase db, Account account) {
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, account.name);
- values.put(ACCOUNTS_TYPE, account.type);
- return db.insert(
- TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
- }
+ long insertSharedAccount(Account account) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, account.name);
+ values.put(ACCOUNTS_TYPE, account.type);
+ return db.insert(
+ TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
+ }
- static boolean deleteSharedAccount(SQLiteDatabase db, Account account) {
- return db
- .delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
- new String[]{account.name, account.type}) > 0;
- }
+ boolean deleteSharedAccount(Account account) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ return db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
+ new String[]{account.name, account.type}) > 0;
+ }
- static int renameSharedAccount(SQLiteDatabase db, Account account, String newName) {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, newName);
- return db.update(TABLE_SHARED_ACCOUNTS,
- values,
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE
- + "=?",
- new String[]{account.name, account.type});
- }
+ int renameSharedAccount(Account account, String newName) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, newName);
+ return db.update(TABLE_SHARED_ACCOUNTS,
+ values,
+ ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
+ new String[] {account.name, account.type});
+ }
- static List<Account> getSharedAccounts(SQLiteDatabase db) {
- ArrayList<Account> accountList = new ArrayList<>();
- Cursor cursor = null;
- try {
- cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{
- ACCOUNTS_NAME, ACCOUNTS_TYPE},
- null, null, null, null, null);
- if (cursor != null && cursor.moveToFirst()) {
- int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
- int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
- do {
- accountList.add(new Account(cursor.getString(nameIndex),
- cursor.getString(typeIndex)));
- } while (cursor.moveToNext());
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return accountList;
- }
-
- static long findSharedAccountId(SQLiteDatabase db, Account account) {
- Cursor cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{
- ACCOUNTS_ID},
- "name=? AND type=?", new String[]{account.name, account.type}, null, null,
- null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- static long findAccountLastAuthenticatedTime(SQLiteDatabase db, Account account) {
- return DatabaseUtils.longForQuery(
- db,
- "SELECT " + AccountsDb.ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
- + " FROM " +
- TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
- + ACCOUNTS_TYPE + "=?",
- new String[] {account.name, account.type});
- }
-
- static boolean updateAccountLastAuthenticatedTime(SQLiteDatabase db, Account account) {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
- int rowCount = db.update(
- TABLE_ACCOUNTS,
- values,
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
- new String[] {
- account.name, account.type
- });
- return rowCount > 0;
- }
-
-
- static void dumpAccountsTable(SQLiteDatabase db, PrintWriter pw) {
- Cursor cursor = db.query(
- TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
- null, null, ACCOUNTS_TYPE, null, null);
- try {
- while (cursor.moveToNext()) {
- // print type,count
- pw.println(cursor.getString(0) + "," + cursor.getString(1));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- static long findAccountId(SQLiteDatabase db, Account account) {
- Cursor cursor = db.query(
- TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
- "name=? AND type=?", new String[]{account.name, account.type}, null, null,
- null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- static Map<Long, Account> findAllAccounts(SQLiteDatabase db) {
- LinkedHashMap<Long, Account> map = new LinkedHashMap<>();
- Cursor cursor = db.query(TABLE_ACCOUNTS,
- new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
- null, null, null, null, ACCOUNTS_ID);
- try {
- while (cursor.moveToNext()) {
- final long accountId = cursor.getLong(0);
- final String accountType = cursor.getString(1);
- final String accountName = cursor.getString(2);
-
- final Account account = new Account(accountName, accountType);
- map.put(accountId, account);
- }
- } finally {
- cursor.close();
- }
- return map;
- }
-
- static String findAccountPreviousName(SQLiteDatabase db, Account account) {
- Cursor cursor = db.query(
- TABLE_ACCOUNTS,
- new String[]{ACCOUNTS_PREVIOUS_NAME},
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE
- + "=?",
- new String[]{account.name, account.type},
- null,
- null,
- null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getString(0);
- }
- } finally {
- cursor.close();
- }
- return null;
- }
-
- static long insertAccount(SQLiteDatabase db, Account account, long accountId) {
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_ID, accountId);
- values.put(ACCOUNTS_NAME, account.name);
- values.put(ACCOUNTS_TYPE, account.type);
- values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
- return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
- }
-
- static boolean renameAccount(SQLiteDatabase db, long accountId, String newName,
- String previousName) {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, newName);
- values.put(ACCOUNTS_PREVIOUS_NAME, previousName);
- final String[] argsAccountId = {String.valueOf(accountId)};
- return db.update(
- TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
- }
-
- static boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(SQLiteDatabase db, long accountId,
- String authTokenType, long uid) {
- return db.delete(TABLE_GRANTS,
- GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
- + GRANTS_GRANTEE_UID + "=?",
- new String[]{String.valueOf(accountId), authTokenType, String.valueOf(uid)})
- > 0;
- }
-
- static List<Integer> findAllUidGrants(SQLiteDatabase db) {
- List<Integer> result = new ArrayList<>();
- final Cursor cursor = db.query(TABLE_GRANTS,
- new String[]{GRANTS_GRANTEE_UID},
- null, null, GRANTS_GRANTEE_UID, null, null);
- try {
- while (cursor.moveToNext()) {
- final int uid = cursor.getInt(0);
- result.add(uid);
- }
- } finally {
- cursor.close();
- }
- return result;
- }
-
- static long findMatchingGrantsCount(SQLiteDatabase db,
- int uid, String authTokenType, Account account) {
- String[] args = {String.valueOf(uid), authTokenType,
- account.name, account.type};
- return DatabaseUtils
- .longForQuery(db, COUNT_OF_MATCHING_GRANTS, args);
- }
-
- static long findMatchingGrantsCountAnyToken(SQLiteDatabase db,
- int uid, Account account) {
- String[] args = {String.valueOf(uid), account.name, account.type};
- return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args);
- }
-
- static long insertGrant(SQLiteDatabase db, long accountId, String authTokenType, int uid) {
- ContentValues values = new ContentValues();
- values.put(GRANTS_ACCOUNTS_ID, accountId);
- values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
- values.put(GRANTS_GRANTEE_UID, uid);
- return db.insert(
- TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
- }
-
- static boolean deleteGrantsByUid(SQLiteDatabase db, int uid) {
- return db.delete(
- TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
- new String[]{Integer.toString(uid)}) > 0;
- }
-
- static long insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid) {
- ContentValues values = new ContentValues();
- values.put(META_KEY,
- META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
- values.put(META_VALUE, uid);
- return db.insert(TABLE_META, null, values);
- }
-
- static long insertOrReplaceMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType,
- int uid) {
- ContentValues values = new ContentValues();
- values.put(META_KEY,
- META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
- values.put(META_VALUE, uid);
- return db.insertWithOnConflict(TABLE_META, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
- }
-
- static Map<String, Integer> findMetaAuthUid(SQLiteDatabase db) {
- Cursor metaCursor = db.query(
- TABLE_META,
- new String[]{META_KEY, META_VALUE},
- SELECTION_META_BY_AUTHENTICATOR_TYPE,
- new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"},
- null /* groupBy */,
- null /* having */,
- META_KEY);
- Map<String, Integer> map = new LinkedHashMap<>();
- try {
- while (metaCursor.moveToNext()) {
- String type = TextUtils
- .split(metaCursor.getString(0), META_KEY_DELIMITER)[1];
- String uidStr = metaCursor.getString(1);
- if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) {
- // Should never happen.
- Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type)
- + ", uid empty: " + TextUtils.isEmpty(uidStr));
- continue;
- }
- int uid = Integer.parseInt(metaCursor.getString(1));
- map.put(type, uid);
- }
- } finally {
- metaCursor.close();
- }
- return map;
- }
-
- static boolean deleteMetaByAuthTypeAndUid(SQLiteDatabase db, String type, int uid) {
- return db.delete(
- TABLE_META,
- META_KEY + "=? AND " + META_VALUE + "=?",
- new String[]{
- META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type,
- String.valueOf(uid)}
- ) > 0;
- }
-
- static List<Pair<String, Integer>> findAllAccountGrants(SQLiteDatabase db) {
- try (Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null)) {
- if (cursor == null || !cursor.moveToFirst()) {
- return Collections.emptyList();
- }
- List<Pair<String, Integer>> results = new ArrayList<>();
+ List<Account> getSharedAccounts() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ ArrayList<Account> accountList = new ArrayList<>();
+ Cursor cursor = null;
+ try {
+ cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
+ null, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
+ int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
do {
- final String accountName = cursor.getString(0);
- final int uid = cursor.getInt(1);
- results.add(Pair.create(accountName, uid));
+ accountList.add(new Account(cursor.getString(nameIndex),
+ cursor.getString(typeIndex)));
} while (cursor.moveToNext());
- return results;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
}
}
+ return accountList;
+ }
- static DeDatabaseHelper create(
- Context context,
- int userId,
- File preNDatabaseFile,
- File deDatabaseFile) {
- boolean newDbExists = deDatabaseFile.exists();
- DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId,
- deDatabaseFile.getPath());
- // If the db just created, and there is a legacy db, migrate it
- if (!newDbExists && preNDatabaseFile.exists()) {
- // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION
- PreNDatabaseHelper
- preNDatabaseHelper = new PreNDatabaseHelper(context, userId,
- preNDatabaseFile.getPath());
- // Open the database to force upgrade if required
- preNDatabaseHelper.getWritableDatabase();
- preNDatabaseHelper.close();
- // Move data without SPII to DE
- deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile);
+ long findSharedAccountId(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{
+ ACCOUNTS_ID},
+ "name=? AND type=?", new String[]{account.name, account.type}, null, null,
+ null);
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getLong(0);
}
- return deDatabaseHelper;
+ return -1;
+ } finally {
+ cursor.close();
}
}
- static class PreNDatabaseHelper extends SQLiteOpenHelper {
+ long findAccountLastAuthenticatedTime(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ return DatabaseUtils.longForQuery(db,
+ "SELECT " + AccountsDb.ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
+ + " FROM " + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
+ + ACCOUNTS_TYPE + "=?",
+ new String[] {account.name, account.type});
+ }
+
+ boolean updateAccountLastAuthenticatedTime(Account account) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
+ int rowCount = db.update(TABLE_ACCOUNTS,
+ values,
+ ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
+ new String[] { account.name, account.type });
+ return rowCount > 0;
+ }
+
+ void dumpDeAccountsTable(PrintWriter pw) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ Cursor cursor = db.query(
+ TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
+ null, null, ACCOUNTS_TYPE, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ // print type,count
+ pw.println(cursor.getString(0) + "," + cursor.getString(1));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ long findDeAccountId(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ String[] columns = {ACCOUNTS_ID};
+ String selection = "name=? AND type=?";
+ String[] selectionArgs = {account.name, account.type};
+ try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
+ null, null, null)) {
+ if (cursor.moveToNext()) {
+ return cursor.getLong(0);
+ }
+ return -1;
+ }
+ }
+
+ Map<Long, Account> findAllDeAccounts() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ LinkedHashMap<Long, Account> map = new LinkedHashMap<>();
+ String[] columns = {ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME};
+ try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns,
+ null, null, null, null, ACCOUNTS_ID)) {
+ while (cursor.moveToNext()) {
+ final long accountId = cursor.getLong(0);
+ final String accountType = cursor.getString(1);
+ final String accountName = cursor.getString(2);
+
+ final Account account = new Account(accountName, accountType);
+ map.put(accountId, account);
+ }
+ }
+ return map;
+ }
+
+ String findDeAccountPreviousName(Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ String[] columns = {ACCOUNTS_PREVIOUS_NAME};
+ String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
+ String[] selectionArgs = {account.name, account.type};
+ try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
+ null, null, null)) {
+ if (cursor.moveToNext()) {
+ return cursor.getString(0);
+ }
+ }
+ return null;
+ }
+
+ long insertDeAccount(Account account, long accountId) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_ID, accountId);
+ values.put(ACCOUNTS_NAME, account.name);
+ values.put(ACCOUNTS_TYPE, account.type);
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
+ return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
+ }
+
+ boolean renameDeAccount(long accountId, String newName, String previousName) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, newName);
+ values.put(ACCOUNTS_PREVIOUS_NAME, previousName);
+ final String[] argsAccountId = {String.valueOf(accountId)};
+ return db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
+ }
+
+ boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId,
+ String authTokenType, long uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ return db.delete(TABLE_GRANTS,
+ GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
+ + GRANTS_GRANTEE_UID + "=?",
+ new String[] {String.valueOf(accountId), authTokenType, String.valueOf(uid)}) > 0;
+ }
+
+ List<Integer> findAllUidGrants() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ List<Integer> result = new ArrayList<>();
+ final Cursor cursor = db.query(TABLE_GRANTS,
+ new String[]{GRANTS_GRANTEE_UID},
+ null, null, GRANTS_GRANTEE_UID, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final int uid = cursor.getInt(0);
+ result.add(uid);
+ }
+ } finally {
+ cursor.close();
+ }
+ return result;
+ }
+
+ long findMatchingGrantsCount(int uid, String authTokenType, Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ String[] args = {String.valueOf(uid), authTokenType, account.name, account.type};
+ return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args);
+ }
+
+ long findMatchingGrantsCountAnyToken(int uid, Account account) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ String[] args = {String.valueOf(uid), account.name, account.type};
+ return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args);
+ }
+
+ long insertGrant(long accountId, String authTokenType, int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(GRANTS_ACCOUNTS_ID, accountId);
+ values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
+ values.put(GRANTS_GRANTEE_UID, uid);
+ return db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
+ }
+
+ boolean deleteGrantsByUid(int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ return db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
+ new String[] {Integer.toString(uid)}) > 0;
+ }
+
+ long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(META_KEY,
+ META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
+ values.put(META_VALUE, uid);
+ return db.insertWithOnConflict(TABLE_META, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ Map<String, Integer> findMetaAuthUid() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ Cursor metaCursor = db.query(
+ TABLE_META,
+ new String[]{META_KEY, META_VALUE},
+ SELECTION_META_BY_AUTHENTICATOR_TYPE,
+ new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"},
+ null /* groupBy */,
+ null /* having */,
+ META_KEY);
+ Map<String, Integer> map = new LinkedHashMap<>();
+ try {
+ while (metaCursor.moveToNext()) {
+ String type = TextUtils
+ .split(metaCursor.getString(0), META_KEY_DELIMITER)[1];
+ String uidStr = metaCursor.getString(1);
+ if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) {
+ // Should never happen.
+ Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type)
+ + ", uid empty: " + TextUtils.isEmpty(uidStr));
+ continue;
+ }
+ int uid = Integer.parseInt(metaCursor.getString(1));
+ map.put(type, uid);
+ }
+ } finally {
+ metaCursor.close();
+ }
+ return map;
+ }
+
+ boolean deleteMetaByAuthTypeAndUid(String type, int uid) {
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ return db.delete(
+ TABLE_META,
+ META_KEY + "=? AND " + META_VALUE + "=?",
+ new String[]{
+ META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type,
+ String.valueOf(uid)}
+ ) > 0;
+ }
+
+ /**
+ * Returns list of all grants as {@link Pair pairs} of account name and UID.
+ */
+ List<Pair<String, Integer>> findAllAccountGrants() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ try (Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ return Collections.emptyList();
+ }
+ List<Pair<String, Integer>> results = new ArrayList<>();
+ do {
+ final String accountName = cursor.getString(0);
+ final int uid = cursor.getInt(1);
+ results.add(Pair.create(accountName, uid));
+ } while (cursor.moveToNext());
+ return results;
+ }
+ }
+
+ private static class PreNDatabaseHelper extends SQLiteOpenHelper {
private final Context mContext;
private final int mUserId;
- public PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
+ PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION);
mContext = context;
mUserId = userId;
@@ -982,7 +966,7 @@
}
private void addDebugTable(SQLiteDatabase db) {
- DebugDbHelper.createDebugTable(db);
+ DeDatabaseHelper.createDebugTable(db);
}
private void createAccountsDeletionTrigger(SQLiteDatabase db) {
@@ -1007,10 +991,18 @@
+ "," + GRANTS_GRANTEE_UID + "))");
}
+ static long insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid) {
+ ContentValues values = new ContentValues();
+ values.put(META_KEY,
+ META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
+ values.put(META_VALUE, uid);
+ return db.insert(TABLE_META, null, values);
+ }
+
private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db,
Map<String, Integer> authTypeAndUIDMap) {
for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) {
- DeDatabaseHelper.insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
+ insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
}
}
@@ -1078,68 +1070,8 @@
}
}
- static class DebugDbHelper{
- private DebugDbHelper() {
- }
-
-
- private static void createDebugTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
- + ACCOUNTS_ID + " INTEGER,"
- + DEBUG_TABLE_ACTION_TYPE + " TEXT NOT NULL, "
- + DEBUG_TABLE_TIMESTAMP + " DATETIME,"
- + DEBUG_TABLE_CALLER_UID + " INTEGER NOT NULL,"
- + DEBUG_TABLE_TABLE_NAME + " TEXT NOT NULL,"
- + DEBUG_TABLE_KEY + " INTEGER PRIMARY KEY)");
- db.execSQL("CREATE INDEX timestamp_index ON " + TABLE_DEBUG + " ("
- + DEBUG_TABLE_TIMESTAMP + ")");
- }
-
- static SQLiteStatement compileSqlStatementForLogging(SQLiteDatabase db) {
- String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
- + " VALUES (?,?,?,?,?,?)";
- return db.compileStatement(sql);
- }
-
- static int getDebugTableRowCount(SQLiteDatabase db) {
- String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
- return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
- }
-
- /*
- * Finds the row key where the next insertion should take place. This should
- * be invoked only if the table has reached its full capacity.
- */
- static int getDebugTableInsertionPoint(SQLiteDatabase db) {
- // This query finds the smallest timestamp value (and if 2 records have
- // same timestamp, the choose the lower id).
- String queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY +
- " FROM " + TABLE_DEBUG +
- " ORDER BY " + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY +
- " LIMIT 1";
- return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
- }
-
- static void dumpDebugTable(SQLiteDatabase db, PrintWriter pw) {
- Cursor cursor = db.query(TABLE_DEBUG, null,
- null, null, null, null, DEBUG_TABLE_TIMESTAMP);
- pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key");
- pw.println("Accounts History");
- try {
- while (cursor.moveToNext()) {
- // print type,count
- pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," +
- cursor.getString(2) + "," + cursor.getString(3) + ","
- + cursor.getString(4) + "," + cursor.getString(5));
- }
- } finally {
- cursor.close();
- }
- }
-
- }
-
- static List<Account> findCeAccountsNotInDe(SQLiteDatabase db) {
+ List<Account> findCeAccountsNotInDe() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
// Select accounts from CE that do not exist in DE
Cursor cursor = db.rawQuery(
"SELECT " + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
@@ -1161,14 +1093,110 @@
}
}
- static boolean deleteCeAccount(SQLiteDatabase db, long accountId) {
+ boolean deleteCeAccount(long accountId) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
return db.delete(
CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
}
+ boolean isCeDatabaseAttached() {
+ return mDeDatabase.mCeAttached;
+ }
+
+ void beginTransaction() {
+ mDeDatabase.getWritableDatabase().beginTransaction();
+ }
+
+ void setTransactionSuccessful() {
+ mDeDatabase.getWritableDatabase().setTransactionSuccessful();
+ }
+
+ void endTransaction() {
+ mDeDatabase.getWritableDatabase().endTransaction();
+ }
+
+ void attachCeDatabase(File ceDbFile) {
+ CeDatabaseHelper.create(mContext, mPreNDatabaseFile, ceDbFile);
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb");
+ mDeDatabase.mCeAttached = true;
+ }
+
+ /*
+ * Finds the row key where the next insertion should take place. Returns number of rows
+ * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
+ */
+ int calculateDebugTableInsertionPoint() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
+ int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
+ if (size < MAX_DEBUG_DB_SIZE) {
+ return size;
+ }
+
+ // This query finds the smallest timestamp value (and if 2 records have
+ // same timestamp, the choose the lower id).
+ queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY +
+ " FROM " + TABLE_DEBUG +
+ " ORDER BY " + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY +
+ " LIMIT 1";
+ return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
+ }
+
+ SQLiteStatement compileSqlStatementForLogging() {
+ // TODO b/31708085 Fix debug logging - it eagerly opens database for write without a need
+ SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
+ + " VALUES (?,?,?,?,?,?)";
+ return db.compileStatement(sql);
+ }
+
+ void dumpDebugTable(PrintWriter pw) {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_DEBUG, null,
+ null, null, null, null, DEBUG_TABLE_TIMESTAMP);
+ pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key");
+ pw.println("Accounts History");
+ try {
+ while (cursor.moveToNext()) {
+ // print type,count
+ pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," +
+ cursor.getString(2) + "," + cursor.getString(3) + ","
+ + cursor.getString(4) + "," + cursor.getString(5));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void close() {
+ mDeDatabase.close();
+ }
+
static void deleteDbFileWarnIfFailed(File dbFile) {
if (!SQLiteDatabase.deleteDatabase(dbFile)) {
Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
}
}
+
+ public static AccountsDb create(Context context, int userId, File preNDatabaseFile,
+ File deDatabaseFile) {
+ boolean newDbExists = deDatabaseFile.exists();
+ DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId,
+ deDatabaseFile.getPath());
+ // If the db just created, and there is a legacy db, migrate it
+ if (!newDbExists && preNDatabaseFile.exists()) {
+ // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION
+ PreNDatabaseHelper
+ preNDatabaseHelper = new PreNDatabaseHelper(context, userId,
+ preNDatabaseFile.getPath());
+ // Open the database to force upgrade if required
+ preNDatabaseHelper.getWritableDatabase();
+ preNDatabaseHelper.close();
+ // Move data without SPII to DE
+ deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile);
+ }
+ return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
+ }
+
}
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 e5b611c..4568da4 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;
@@ -182,6 +185,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -215,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;
@@ -252,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;
@@ -275,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;
@@ -287,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;
@@ -1120,20 +1128,29 @@
*/
final AppOpsService mAppOpsService;
- /**
- * Current global configuration information. Contains general settings for the entire system,
- * also corresponds to the merged configuration of the default display.
- */
- Configuration mGlobalConfiguration = new Configuration();
-
/** Current sequencing integer of the configuration, for skipping old configurations. */
private int mConfigurationSeq;
/**
- * Temp object used when global configuration is updated. It is also sent to outer world
- * instead of {@link #mGlobalConfiguration} 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();
+ private static final class UpdateConfigurationResult {
+ // Configuration changes that were updated.
+ int changes;
+ // If the activity was relaunched to match the new configuration.
+ boolean activityRelaunched;
+
+ void reset() {
+ changes = 0;
+ activityRelaunched = false;
+ }
+ }
boolean mSuppressResizeConfigChanges;
@@ -1360,7 +1377,6 @@
boolean mSupportsFreeformWindowManagement;
boolean mSupportsPictureInPicture;
boolean mSupportsLeanbackOnly;
- Rect mDefaultPinnedStackBounds;
IActivityController mController = null;
boolean mControllerIsAMonkey = false;
String mProfileApp = null;
@@ -1380,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;
@@ -1567,6 +1589,14 @@
final boolean mPermissionReviewRequired;
+ /**
+ * Current global configuration information. Contains general settings for the entire system,
+ * also corresponds to the merged configuration of the default display.
+ */
+ Configuration getGlobalConfiguration() {
+ return mStackSupervisor.getConfiguration();
+ }
+
final class KillHandler extends Handler {
static final int KILL_PROCESS_GROUP_MSG = 4000;
@@ -2314,7 +2344,7 @@
callingPackage = r.info.getComponentName();
if (mInVrMode != vrMode) {
mInVrMode = vrMode;
- mShowDialogs = shouldShowDialogs(mGlobalConfiguration, mInVrMode);
+ mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), mInVrMode);
if (r.app != null) {
ProcessRecord proc = r.app;
if (proc.vrThreadTid > 0) {
@@ -2690,15 +2720,16 @@
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
- mGlobalConfiguration.setToDefaults();
- mGlobalConfiguration.setLocales(LocaleList.getDefault());
+ mTempConfig.setToDefaults();
+ mTempConfig.setLocales(LocaleList.getDefault());
+ mConfigurationSeq = mTempConfig.seq = 1;
- mConfigurationSeq = mGlobalConfiguration.seq = 1;
mProcessCpuTracker.init();
+ mStackSupervisor = new ActivityStackSupervisor(this);
+ mStackSupervisor.onConfigurationChanged(mTempConfig);
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
- mStackSupervisor = new ActivityStackSupervisor(this);
mActivityStarter = new ActivityStarter(this, mStackSupervisor);
mRecentTasks = new RecentTasks(this, mStackSupervisor);
@@ -3086,7 +3117,7 @@
synchronized (this) {
ActivityRecord r = mStackSupervisor.isInAnyStackLocked(token);
if (r != null) {
- r.task.stack.notifyActivityDrawnLocked(r);
+ r.getStack().notifyActivityDrawnLocked(r);
}
}
}
@@ -3130,8 +3161,9 @@
}
final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- if (mGlobalConfiguration.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > mGlobalConfiguration.smallestScreenWidthDp) {
+ final Configuration globalConfig = getGlobalConfiguration();
+ if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
final Message msg = Message.obtain();
msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
msg.obj = r;
@@ -4040,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");
}
@@ -4050,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;
}
}
}
@@ -4733,23 +4757,12 @@
if (r == null) {
return;
}
- TaskRecord task = r.task;
- if (task != null && (!task.mFullscreen || !task.stack.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(
- mGlobalConfiguration, 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);
}
}
@@ -4774,7 +4787,8 @@
final long origId = Binder.clearCallingIdentity();
try {
r.forceNewConfig = true;
- r.task.stack.ensureActivityConfigurationLocked(r, 0, false);
+ r.ensureActivityConfigurationLocked(0 /* globalChanges */,
+ false /* preserveWindow */);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -4820,7 +4834,7 @@
}
if (mController != null) {
// Find the first activity that is not finishing.
- ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
+ ActivityRecord next = r.getStack().topRunningActivityLocked(token, 0);
if (next != null) {
// ask watcher if this is allowed
boolean resumeOK = true;
@@ -4854,7 +4868,7 @@
Slog.i(TAG, "Removing task failed to finish activity");
}
} else {
- res = tr.stack.requestFinishActivityLocked(token, resultCode,
+ res = tr.getStack().requestFinishActivityLocked(token, resultCode,
resultData, "app-request", true);
if (!res) {
Slog.i(TAG, "Failed to finish by app-request");
@@ -4888,7 +4902,7 @@
for (int i = 0; i < activities.size(); i++) {
ActivityRecord r = activities.get(i);
if (!r.finishing && r.isInStackLocked()) {
- r.task.stack.finishActivityLocked(r, Activity.RESULT_CANCELED,
+ r.getStack().finishActivityLocked(r, Activity.RESULT_CANCELED,
null, "finish-heavy", true);
}
}
@@ -4924,7 +4938,7 @@
final long origId = Binder.clearCallingIdentity();
ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
- r.task.stack.finishSubActivityLocked(r, resultWho, requestCode);
+ r.getStack().finishSubActivityLocked(r, resultWho, requestCode);
}
Binder.restoreCallingIdentity(origId);
}
@@ -4948,7 +4962,7 @@
mStackSupervisor.showLockTaskToast();
return false;
}
- return task.stack.finishActivityAffinityLocked(r);
+ return task.getStack().finishActivityAffinityLocked(r);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -4979,7 +4993,7 @@
if (r == null) {
return false;
}
- return r.task.stack.safelyDestroyActivityLocked(r, "app-req");
+ return r.getStack().safelyDestroyActivityLocked(r, "app-req");
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -6126,6 +6140,8 @@
ProcessList.INVALID_ADJ, callerWillRestart, true, doit, evenPersistent,
packageName == null ? ("stop user " + userId) : ("stop " + packageName));
+ didSomething |= mActivityStarter.clearPendingActivityLaunchesLocked(packageName);
+
if (mStackSupervisor.finishDisabledPackageActivitiesLocked(
packageName, null, doit, evenPersistent, userId)) {
if (!doit) {
@@ -6485,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;
@@ -6527,7 +6543,7 @@
PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
- + processName + " with config " + mGlobalConfiguration);
+ + processName + " with config " + getGlobalConfiguration());
ApplicationInfo appInfo = app.instrumentationInfo != null
? app.instrumentationInfo : app.info;
app.compat = compatibilityInfoForPackageLocked(appInfo);
@@ -6552,7 +6568,7 @@
app.instrumentationUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
- new Configuration(mGlobalConfiguration), app.compat,
+ new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial);
@@ -6880,10 +6896,7 @@
public final void activityResumed(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized(this) {
- ActivityStack stack = ActivityRecord.getStackLocked(token);
- if (stack != null) {
- stack.activityResumedLocked(token);
- }
+ ActivityRecord.activityResumedLocked(token);
}
Binder.restoreCallingIdentity(origId);
}
@@ -6913,9 +6926,9 @@
final long origId = Binder.clearCallingIdentity();
synchronized (this) {
- ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
- r.task.stack.activityStoppedLocked(r, icicle, persistentState, description);
+ r.activityStoppedLocked(icicle, persistentState, description);
}
}
@@ -7576,7 +7589,7 @@
// current bounds.
final ActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID);
final Rect bounds = (pinnedStack != null)
- ? pinnedStack.mBounds : mDefaultPinnedStackBounds;
+ ? pinnedStack.mBounds : getDefaultPictureInPictureBounds(DEFAULT_DISPLAY);
mStackSupervisor.moveActivityToPinnedStackLocked(
r, "enterPictureInPictureMode", bounds);
@@ -7586,6 +7599,85 @@
}
}
+ @Override
+ public Rect getDefaultPictureInPictureBounds(int displayId) {
+ 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 = mStackSupervisor.getDisplayRealMetrics(displayId);
+ 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(displayId, maxBounds);
+ Gravity.apply(mDefaultPinnedStackGravity, stackWidth, stackHeight,
+ maxBounds, 0, 0, defaultBounds);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ return defaultBounds;
+ }
+
+ @Override
+ public Rect getPictureInPictureMovementBounds(int displayId) {
+ final long origId = Binder.clearCallingIdentity();
+ final Rect maxBounds = new Rect();
+ try {
+ synchronized(this) {
+ if (!mSupportsPictureInPicture) {
+ return new Rect();
+ }
+
+ getPictureInPictureBounds(displayId, 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(int displayId, Rect outRect) {
+ // Convert the insets to for the current display state
+ final DisplayMetrics dm = mStackSupervisor.getDisplayRealMetrics(displayId);
+ 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 Point displaySize = mStackSupervisor.getDisplayRealSize(displayId);
+ final Rect insets = new Rect();
+ mWindowManager.getStableInsets(displayId, insets);
+
+ // Calculate the insets from the system decorations and apply the gravity
+ outRect.set(insets.left + insetsLR, insets.top + insetsTB,
+ displaySize.x - insets.right - insetsLR,
+ displaySize.y - insets.bottom - insetsTB);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to calculate PIP movement bounds", e);
+ }
+ }
+
// =========================================================
// PROCESS INFO
// =========================================================
@@ -9059,7 +9151,7 @@
rti.origActivity = tr.origActivity;
rti.realActivity = tr.realActivity;
rti.description = tr.lastDescription;
- rti.stackId = tr.stack != null ? tr.stack.mStackId : -1;
+ rti.stackId = tr.getStackId();
rti.userId = tr.userId;
rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
rti.firstActiveTime = tr.firstActiveTime;
@@ -9188,15 +9280,15 @@
continue;
}
}
+ final ActivityStack stack = tr.getStack();
if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) {
- if (tr.stack != null && tr.stack.isHomeStack()) {
+ if (stack != null && stack.isHomeStack()) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, home stack task: " + tr);
continue;
}
}
if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) {
- final ActivityStack stack = tr.stack;
if (stack != null && stack.isDockedStack() && stack.topTask() == tr) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, top task in docked stack: " + tr);
@@ -9204,7 +9296,7 @@
}
}
if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {
- if (tr.stack != null && tr.stack.isPinnedStack()) {
+ if (stack != null && stack.isPinnedStack()) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, pinned stack task: " + tr);
continue;
@@ -9308,17 +9400,9 @@
}
}
- // Use the full screen as the context for the task thumbnail
- final Point displaySize = new Point();
- final TaskThumbnailInfo thumbnailInfo = new TaskThumbnailInfo();
- r.task.stack.getDisplaySize(displaySize);
- thumbnailInfo.taskWidth = displaySize.x;
- thumbnailInfo.taskHeight = displaySize.y;
- thumbnailInfo.screenOrientation = mGlobalConfiguration.orientation;
-
TaskRecord task = new TaskRecord(this,
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
- ainfo, intent, description, thumbnailInfo);
+ ainfo, intent, description, new TaskThumbnailInfo());
int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
if (trimIdx >= 0) {
@@ -9335,7 +9419,7 @@
task.inRecents = true;
mRecentTasks.add(task);
- r.task.stack.addTask(task, false, "addAppTask");
+ r.getStack().addTask(task, false, "addAppTask");
task.setLastThumbnailLocked(thumbnail);
task.freeLastThumbnail();
@@ -9401,7 +9485,7 @@
// - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
// that task to freeform
// - otherwise the task is not moved
- int stackId = task.stack.mStackId;
+ int stackId = task.getStackId();
if (!StackId.isTaskResizeAllowed(stackId)) {
throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
}
@@ -9411,7 +9495,7 @@
stackId = FREEFORM_WORKSPACE_STACK_ID;
}
boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
- if (stackId != task.stack.mStackId) {
+ if (stackId != task.getStackId()) {
mStackSupervisor.moveTaskToStackUncheckedLocked(
task, stackId, ON_TOP, !FORCE_FOCUS, "resizeTask");
preserveWindow = false;
@@ -9438,7 +9522,7 @@
Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");
return rect;
}
- if (task.stack != null) {
+ if (task.getStack() != null) {
// Return the bounds from window manager since it will be adjusted for various
// things like the presense of a docked stack for tasks that aren't resizeable.
mWindowManager.getTaskBounds(task.taskId, rect);
@@ -9553,7 +9637,7 @@
for (int i = 0; i < procsToKill.size(); i++) {
ProcessRecord pr = procsToKill.get(i);
if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- && pr.curReceiver == null) {
+ && pr.curReceivers.isEmpty()) {
pr.kill("remove task", true);
} else {
// We delay killing processes that are not in the background or running a receiver.
@@ -9772,14 +9856,6 @@
}
@Override
- public void deleteActivityContainer(IActivityContainer container) throws RemoteException {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "deleteActivityContainer()");
- synchronized (this) {
- mStackSupervisor.deleteActivityContainer(container);
- }
- }
-
- @Override
public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "createStackOnDisplay()");
synchronized (this) {
@@ -9800,7 +9876,7 @@
if (stack != null && stack.mActivityContainer.isAttachedLocked()) {
return stack.mActivityContainer.getDisplayId();
}
- return Display.DEFAULT_DISPLAY;
+ return DEFAULT_DISPLAY;
}
}
@@ -10096,7 +10172,8 @@
synchronized (this) {
final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
- return tr != null && tr.stack != null && tr.stack.isHomeStack();
+ final ActivityStack stack = tr != null ? tr.getStack() : null;
+ return stack != null && stack.isHomeStack();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -12443,7 +12520,7 @@
}
final boolean translucentChanged = r.changeWindowTranslucency(true);
if (translucentChanged) {
- r.task.stack.releaseBackgroundResources(r);
+ r.getStack().releaseBackgroundResources(r);
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
mWindowManager.setAppFullscreen(token, true);
@@ -12470,7 +12547,7 @@
}
final boolean translucentChanged = r.changeWindowTranslucency(false);
if (translucentChanged) {
- r.task.stack.convertActivityToTranslucent(r);
+ r.getStack().convertActivityToTranslucent(r);
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
mWindowManager.setAppFullscreen(token, false);
@@ -13152,8 +13229,8 @@
// This happens before any activities are started, so we can change global configuration
// in-place.
updateConfigurationLocked(configuration, null, true);
- if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Initial config: " + mGlobalConfiguration);
+ final Configuration globalConfig = getGlobalConfiguration();
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Initial config: " + globalConfig);
// Load resources only after the current configuration has been set.
final Resources res = mContext.getResources();
@@ -13162,17 +13239,20 @@
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(
com.android.internal.R.bool.config_customUserSwitchUi);
- if ((mGlobalConfiguration.uiMode & UI_MODE_TYPE_TELEVISION)
- == UI_MODE_TYPE_TELEVISION) {
+ if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
mFullscreenThumbnailScale = (float) res
.getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
- (float) mGlobalConfiguration.screenWidthDp;
+ (float) globalConfig.screenWidthDp;
} else {
mFullscreenThumbnailScale = res.getFraction(
com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
@@ -14024,9 +14104,10 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
(new ActivityManagerShellCommand(this, false)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
@@ -14245,11 +14326,19 @@
}
} else if ("locks".equals(cmd)) {
LockGuard.dump(fd, pw, args);
+ } else if ("pip".equals(cmd)) {
+ Rect bounds = getDefaultPictureInPictureBounds(DEFAULT_DISPLAY);
+ pw.print("defaultBounds="); bounds.printShortString(pw);
+ pw.println();
+ bounds = getPictureInPictureMovementBounds(DEFAULT_DISPLAY);
+ pw.print("movementBounds="); bounds.printShortString(pw);
+ pw.println();
} else {
// Dumping a single activity?
if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll, dumpVisibleStacks)) {
ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
- int res = shell.exec(this, null, fd, null, args, new ResultReceiver(null));
+ int res = shell.exec(this, null, fd, null, args, null,
+ new ResultReceiver(null));
if (res < 0) {
pw.println("Bad activity command, or no activities match: " + cmd);
pw.println("Use -h for help.");
@@ -14765,7 +14854,8 @@
pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess);
}
if (dumpPackage == null) {
- pw.println(" mGlobalConfiguration: " + mGlobalConfiguration);
+ pw.println(" mGlobalConfiguration: " + getGlobalConfiguration());
+ mStackSupervisor.dumpDisplayConfigs(pw, " ");
}
if (dumpAll) {
pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
@@ -15142,7 +15232,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, " ");
}
@@ -15177,7 +15268,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 {
@@ -15710,7 +15801,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();
@@ -15743,7 +15834,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();
@@ -16147,10 +16238,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!");
@@ -17320,9 +17423,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);
@@ -17335,7 +17439,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;
@@ -18006,8 +18111,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:
@@ -18032,8 +18137,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) {
@@ -18098,7 +18203,7 @@
return ActivityManager.BROADCAST_SUCCESS;
}
mStackSupervisor.updateActivityApplicationInfoLocked(aInfo);
- sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REPLACED,
+ sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
new String[] {ssp}, userId);
}
break;
@@ -18748,15 +18853,16 @@
public ConfigurationInfo getDeviceConfigurationInfo() {
ConfigurationInfo config = new ConfigurationInfo();
synchronized (this) {
- config.reqTouchScreen = mGlobalConfiguration.touchscreen;
- config.reqKeyboardType = mGlobalConfiguration.keyboard;
- config.reqNavigation = mGlobalConfiguration.navigation;
- if (mGlobalConfiguration.navigation == Configuration.NAVIGATION_DPAD
- || mGlobalConfiguration.navigation == Configuration.NAVIGATION_TRACKBALL) {
+ final Configuration globalConfig = getGlobalConfiguration();
+ config.reqTouchScreen = globalConfig.touchscreen;
+ config.reqKeyboardType = globalConfig.keyboard;
+ config.reqNavigation = globalConfig.navigation;
+ if (globalConfig.navigation == Configuration.NAVIGATION_DPAD
+ || globalConfig.navigation == Configuration.NAVIGATION_TRACKBALL) {
config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
}
- if (mGlobalConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED
- && mGlobalConfiguration.keyboard != Configuration.KEYBOARD_NOKEYS) {
+ if (globalConfig.keyboard != Configuration.KEYBOARD_UNDEFINED
+ && globalConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
}
config.reqGlEsVersion = GL_ES_VERSION;
@@ -18780,7 +18886,7 @@
public Configuration getConfiguration() {
Configuration ci;
synchronized(this) {
- ci = new Configuration(mGlobalConfiguration);
+ ci = new Configuration(getGlobalConfiguration());
ci.userSetLocale = false;
}
return ci;
@@ -18812,8 +18918,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");
@@ -18838,12 +18943,16 @@
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
FONT_SCALE, 1.0f, userId);
- if (mGlobalConfiguration.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);
}
}
@@ -18867,31 +18976,37 @@
}
@Override
- public void updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
+ public boolean updateConfiguration(Configuration values) {
+ 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();
- if (values != null) {
- Settings.System.clearConfiguration(values);
+ try {
+ if (values != null) {
+ Settings.System.clearConfiguration(values);
+ }
+ updateConfigurationLocked(values, null, false, false /* persistent */,
+ UserHandle.USER_NULL, false /* deferResume */,
+ mTmpUpdateConfigurationResult);
+ return mTmpUpdateConfigurationResult.changes != 0;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- updateConfigurationLocked(values, null, false);
- Binder.restoreCallingIdentity(origId);
}
}
void updateUserConfigurationLocked() {
- final Configuration configuration = new Configuration(mGlobalConfiguration);
+ final Configuration configuration = new Configuration(getGlobalConfiguration());
final int currentUserId = mUserController.getCurrentUserIdLocked();
Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
currentUserId, Settings.System.canWrite(mContext));
@@ -18914,6 +19029,12 @@
// To cache the list of supported system locales
private String[] mSupportedSystemLocales = null;
+ private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
+ boolean initLocale, boolean persistent, int userId, boolean deferResume) {
+ return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
+ deferResume, null /* result */);
+ }
+
/**
* Do either or both things: (1) change the current configuration, and (2)
* make sure the given activity is running with the (now) current
@@ -18925,7 +19046,8 @@
* for that particular user
*/
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
- boolean initLocale, boolean persistent, int userId, boolean deferResume) {
+ boolean initLocale, boolean persistent, int userId, boolean deferResume,
+ UpdateConfigurationResult result) {
int changes = 0;
boolean kept = true;
@@ -18944,14 +19066,19 @@
mWindowManager.continueSurfaceLayout();
}
}
+
+ if (result != null) {
+ result.changes = changes;
+ result.activityRelaunched = !kept;
+ }
return kept;
}
/** 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(mGlobalConfiguration);
- final int changes = mTempGlobalConfig.updateFrom(values);
+ mTempConfig.setTo(getGlobalConfiguration());
+ final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
return 0;
}
@@ -18978,31 +19105,33 @@
}
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
- mTempGlobalConfig.seq = mConfigurationSeq;
+ mTempConfig.seq = mConfigurationSeq;
- mGlobalConfiguration.setTo(mTempGlobalConfig);
- Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempGlobalConfig);
+ // Update stored global config and notify everyone about the change.
+ mStackSupervisor.onConfigurationChanged(mTempConfig);
+
+ 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(mGlobalConfiguration);
+ final Configuration configCopy = new Configuration(mTempConfig);
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = configCopy;
@@ -19010,16 +19139,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 {
@@ -19049,13 +19168,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);
@@ -19080,7 +19302,8 @@
}
if (starting != null) {
- kept = mainStack.ensureActivityConfigurationLocked(starting, changes, false);
+ kept = starting.ensureActivityConfigurationLocked(changes,
+ false /* preserveWindow */);
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes,
@@ -19123,7 +19346,7 @@
synchronized (this) {
ActivityRecord srec = ActivityRecord.forTokenLocked(token);
if (srec != null) {
- return srec.task.stack.shouldUpRecreateTaskLocked(srec, destAffinity);
+ return srec.getStack().shouldUpRecreateTaskLocked(srec, destAffinity);
}
}
return false;
@@ -19135,7 +19358,7 @@
synchronized (this) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
- return r.task.stack.navigateUpToLocked(r, destIntent, resultCode, resultData);
+ return r.getStack().navigateUpToLocked(r, destIntent, resultCode, resultData);
}
return false;
}
@@ -19167,26 +19390,28 @@
// LIFETIME MANAGEMENT
// =========================================================
- // Returns which broadcast queue the app is the current [or imminent] receiver
- // on, or 'null' if the app is not an active broadcast recipient.
- private BroadcastQueue isReceivingBroadcast(ProcessRecord app) {
- BroadcastRecord r = app.curReceiver;
- if (r != null) {
- return r.queue;
+ // Returns whether the app is receiving broadcast.
+ // If receiving, fetch all broadcast queues which the app is
+ // the current [or imminent] receiver on.
+ private boolean isReceivingBroadcastLocked(ProcessRecord app,
+ ArraySet<BroadcastQueue> receivingQueues) {
+ if (!app.curReceivers.isEmpty()) {
+ for (BroadcastRecord r : app.curReceivers) {
+ receivingQueues.add(r.queue);
+ }
+ return true;
}
// It's not the current receiver, but it might be starting up to become one
- synchronized (this) {
- for (BroadcastQueue queue : mBroadcastQueues) {
- r = queue.mPendingBroadcast;
- if (r != null && r.curApp == app) {
- // found it; report which queue it's in
- return queue;
- }
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ final BroadcastRecord r = queue.mPendingBroadcast;
+ if (r != null && r.curApp == app) {
+ // found it; report which queue it's in
+ receivingQueues.add(queue);
}
}
- return null;
+ return !receivingQueues.isEmpty();
}
Association startAssociationLocked(int sourceUid, String sourceProcess, int sourceState,
@@ -19353,7 +19578,7 @@
int schedGroup;
int procState;
boolean foregroundActivities = false;
- BroadcastQueue queue;
+ final ArraySet<BroadcastQueue> queues = new ArraySet<BroadcastQueue>();
if (app == TOP_APP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -19367,13 +19592,13 @@
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
app.adjType = "instrumentation";
procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- } else if ((queue = isReceivingBroadcast(app)) != null) {
+ } else if (isReceivingBroadcastLocked(app, queues)) {
// An app that is currently receiving a broadcast also
// counts as being in the foreground for OOM killer purposes.
// It's placed in a sched group based on the nature of the
// broadcast as reflected by which queue it's active in.
adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = (queue == mFgBroadcastQueue)
+ schedGroup = (queues.contains(mFgBroadcastQueue))
? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
app.adjType = "broadcast";
procState = ActivityManager.PROCESS_STATE_RECEIVER;
@@ -20384,7 +20609,7 @@
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Setting sched group of " + app.processName
+ " to " + app.curSchedGroup);
- if (app.waitingToKill != null && app.curReceiver == null
+ if (app.waitingToKill != null && app.curReceivers.isEmpty()
&& app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
app.kill(app.waitingToKill, true);
success = false;
@@ -20679,8 +20904,11 @@
final long now = SystemClock.elapsedRealtime();
Long lastReported = userState.mProviderLastReportedFg.get(authority);
if (lastReported == null || lastReported < now - 60 * 1000L) {
- mUsageStatsService.reportContentProviderUsage(
- authority, providerPkgName, app.userId);
+ if (mSystemReady) {
+ // Cannot touch the user stats if not system ready
+ mUsageStatsService.reportContentProviderUsage(
+ authority, providerPkgName, app.userId);
+ }
userState.mProviderLastReportedFg.put(authority, now);
}
}
@@ -21321,7 +21549,7 @@
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
if (app.activities.size() == 0
- && app.curReceiver == null && app.services.size() == 0) {
+ && app.curReceivers.isEmpty() && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
+ app.toShortString() + " ("
@@ -21697,6 +21925,13 @@
return mUserController.getCurrentUser();
}
+ String getStartedUserState(int userId) {
+ synchronized (this) {
+ final UserState userState = mUserController.getStartedUserStateLocked(userId);
+ return UserState.stateToString(userState.state);
+ }
+ }
+
@Override
public boolean isUserRunning(int userId, int flags) {
if (!mUserController.isSameProfileGroup(userId, UserHandle.getCallingUserId())
@@ -21833,8 +22068,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();
@@ -22158,7 +22392,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);
}
@@ -22243,4 +22477,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 adf6d36..7a692b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -17,26 +17,107 @@
package com.android.server.am;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.AppGlobals;
+import android.app.IActivityContainer;
+import android.app.IActivityController;
import android.app.IActivityManager;
+import android.app.IInstrumentationWatcher;
+import android.app.IStopUserCallback;
+import android.app.Instrumentation;
+import android.app.ProfilerInfo;
+import android.app.UiAutomationConnection;
+import android.app.usage.ConfigurationStats;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageStatsManager;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.DebugUtils;
+import android.view.IWindowManager;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.Preconditions;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
-class ActivityManagerShellCommand extends ShellCommand {
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
+import static android.app.ActivityManager.RESIZE_MODE_USER;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
+final class ActivityManagerShellCommand extends ShellCommand {
+ public static final String NO_CLASS_ERROR_CODE = "Error type 3";
+ private static final String SHELL_PACKAGE_NAME = "com.android.shell";
+
+ // Is the object moving in a positive direction?
+ private static final boolean MOVING_FORWARD = true;
+ // Is the object moving in the horizontal plan?
+ private static final boolean MOVING_HORIZONTALLY = true;
+ // Is the object current point great then its target point?
+ private static final boolean GREATER_THAN_TARGET = true;
+ // Amount we reduce the stack size by when testing a task re-size.
+ private static final int STACK_BOUNDS_INSET = 10;
+
// IPC interface to activity manager -- don't need to do additional security checks.
final IActivityManager mInterface;
// Internal service impl -- must perform security checks before touching.
final ActivityManagerService mInternal;
+ // Convenience for interacting with package manager.
+ final IPackageManager mPm;
+
+ private int mStartFlags = 0;
+ private boolean mWaitOption = false;
+ private boolean mStopOption = false;
+
+ private int mRepeat = 0;
+ private int mUserId;
+ private String mReceiverPermission;
+
+ private String mProfileFile;
+ private int mSamplingInterval;
+ private boolean mAutoStop;
+ private int mStackId;
+
final boolean mDumping;
ActivityManagerShellCommand(ActivityManagerService service, boolean dumping) {
mInterface = service;
mInternal = service;
+ mPm = AppGlobals.getPackageManager();
mDumping = dumping;
}
@@ -48,24 +129,99 @@
PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
+ case "start":
+ case "start-activity":
+ return runStartActivity(pw);
+ case "startservice":
+ case "start-service":
+ return runStartService(pw);
+ case "stopservice":
+ case "stop-service":
+ return runStopService(pw);
+ case "broadcast":
+ return runSendBroadcast(pw);
+ case "instrument":
+ return runInstrument(pw);
+ case "trace-ipc":
+ return runTraceIpc(pw);
+ case "profile":
+ return runProfile(pw);
+ case "dumpheap":
+ return runDumpHeap(pw);
+ case "set-debug-app":
+ return runSetDebugApp(pw);
+ case "clear-debug-app":
+ return runClearDebugApp(pw);
+ case "set-watch-heap":
+ return runSetWatchHeap(pw);
+ case "clear-watch-heap":
+ return runClearWatchHeap(pw);
+ case "bug-report":
+ return runBugReport(pw);
case "force-stop":
return runForceStop(pw);
case "kill":
return runKill(pw);
case "kill-all":
return runKillAll(pw);
- case "write":
- return runWrite(pw);
+ case "monitor":
+ return runMonitor(pw);
+ case "hang":
+ return runHang(pw);
+ case "restart":
+ return runRestart(pw);
+ case "idle-maintenance":
+ return runIdleMaintenance(pw);
+ case "screen-compat":
+ return runScreenCompat(pw);
+ case "package-importance":
+ return runPackageImportance(pw);
+ case "to-uri":
+ return runToUri(pw, 0);
+ case "to-intent-uri":
+ return runToUri(pw, Intent.URI_INTENT_SCHEME);
+ case "to-app-uri":
+ return runToUri(pw, Intent.URI_ANDROID_APP_SCHEME);
+ case "switch-user":
+ return runSwitchUser(pw);
+ case "get-current-user":
+ return runGetCurrentUser(pw);
+ case "start-user":
+ return runStartUser(pw);
+ case "unlock-user":
+ return runUnlockUser(pw);
+ case "stop-user":
+ return runStopUser(pw);
+ case "is-user-stopped":
+ return runIsUserStopped(pw);
+ case "get-started-user-state":
+ return runGetStartedUserState(pw);
case "track-associations":
return runTrackAssociations(pw);
case "untrack-associations":
return runUntrackAssociations(pw);
- case "is-user-stopped":
- return runIsUserStopped(pw);
case "lenient-background-check":
return runLenientBackgroundCheck(pw);
case "get-uid-state":
return getUidState(pw);
+ case "get-config":
+ return runGetConfig(pw);
+ case "suppress-resize-config-changes":
+ return runSuppressResizeConfigChanges(pw);
+ case "set-inactive":
+ return runSetInactive(pw);
+ case "get-inactive":
+ return runGetInactive(pw);
+ case "send-trim-memory":
+ return runSendTrimMemory(pw);
+ case "stack":
+ return runStack(pw);
+ case "task":
+ return runTask(pw);
+ case "write":
+ return runWrite(pw);
+ case "attach-agent":
+ return runAttachAgent(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -75,10 +231,797 @@
return -1;
}
- int runIsUserStopped(PrintWriter pw) {
- int userId = UserHandle.parseUserArg(getNextArgRequired());
- boolean stopped = mInternal.isUserStopped(userId);
- pw.println(stopped);
+ private Intent makeIntent(int defUser) throws URISyntaxException {
+ mStartFlags = 0;
+ mWaitOption = false;
+ mStopOption = false;
+ mRepeat = 0;
+ mProfileFile = null;
+ mSamplingInterval = 0;
+ mAutoStop = false;
+ mUserId = defUser;
+ mStackId = INVALID_STACK_ID;
+
+ return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
+ @Override
+ public boolean handleOption(String opt, ShellCommand cmd) {
+ if (opt.equals("-D")) {
+ mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+ } else if (opt.equals("-N")) {
+ mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING;
+ } else if (opt.equals("-W")) {
+ mWaitOption = true;
+ } else if (opt.equals("-P")) {
+ mProfileFile = getNextArgRequired();
+ mAutoStop = true;
+ } else if (opt.equals("--start-profiler")) {
+ mProfileFile = getNextArgRequired();
+ mAutoStop = false;
+ } else if (opt.equals("--sampling")) {
+ mSamplingInterval = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("-R")) {
+ mRepeat = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("-S")) {
+ mStopOption = true;
+ } else if (opt.equals("--track-allocation")) {
+ mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
+ } else if (opt.equals("--user")) {
+ mUserId = UserHandle.parseUserArg(getNextArgRequired());
+ } else if (opt.equals("--receiver-permission")) {
+ mReceiverPermission = getNextArgRequired();
+ } else if (opt.equals("--stack")) {
+ mStackId = Integer.parseInt(getNextArgRequired());
+ } else {
+ return false;
+ }
+ return true;
+ }
+ });
+ }
+
+ int runStartActivity(PrintWriter pw) throws RemoteException {
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+
+ if (mUserId == UserHandle.USER_ALL) {
+ getErrPrintWriter().println("Error: Can't start service with user 'all'");
+ return 1;
+ }
+
+ String mimeType = intent.getType();
+ if (mimeType == null && intent.getData() != null
+ && "content".equals(intent.getData().getScheme())) {
+ mimeType = mInterface.getProviderMimeType(intent.getData(), mUserId);
+ }
+
+ do {
+ if (mStopOption) {
+ String packageName;
+ if (intent.getComponent() != null) {
+ packageName = intent.getComponent().getPackageName();
+ } else {
+ List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
+ mUserId).getList();
+ if (activities == null || activities.size() <= 0) {
+ getErrPrintWriter().println("Error: Intent does not match any activities: "
+ + intent);
+ return 1;
+ } else if (activities.size() > 1) {
+ getErrPrintWriter().println(
+ "Error: Intent matches multiple activities; can't stop: "
+ + intent);
+ return 1;
+ }
+ packageName = activities.get(0).activityInfo.packageName;
+ }
+ pw.println("Stopping: " + packageName);
+ pw.flush();
+ mInterface.forceStopPackage(packageName, mUserId);
+ try {
+ Thread.sleep(250);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ ProfilerInfo profilerInfo = null;
+
+ if (mProfileFile != null) {
+ ParcelFileDescriptor fd = openOutputFileForSystem(mProfileFile);
+ if (fd == null) {
+ return 1;
+ }
+ profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
+ }
+
+ pw.println("Starting: " + intent);
+ pw.flush();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ IActivityManager.WaitResult result = null;
+ int res;
+ final long startTime = SystemClock.uptimeMillis();
+ ActivityOptions options = null;
+ if (mStackId != INVALID_STACK_ID) {
+ options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(mStackId);
+ }
+ if (mWaitOption) {
+ result = mInterface.startActivityAndWait(null, null, intent, mimeType,
+ null, null, 0, mStartFlags, profilerInfo,
+ options != null ? options.toBundle() : null, mUserId);
+ res = result.result;
+ } else {
+ res = mInterface.startActivityAsUser(null, null, intent, mimeType,
+ null, null, 0, mStartFlags, profilerInfo,
+ options != null ? options.toBundle() : null, mUserId);
+ }
+ final long endTime = SystemClock.uptimeMillis();
+ PrintWriter out = mWaitOption ? pw : getErrPrintWriter();
+ boolean launched = false;
+ switch (res) {
+ case ActivityManager.START_SUCCESS:
+ launched = true;
+ break;
+ case ActivityManager.START_SWITCHES_CANCELED:
+ launched = true;
+ out.println(
+ "Warning: Activity not started because the "
+ + " current activity is being kept for the user.");
+ break;
+ case ActivityManager.START_DELIVERED_TO_TOP:
+ launched = true;
+ out.println(
+ "Warning: Activity not started, intent has "
+ + "been delivered to currently running "
+ + "top-most instance.");
+ break;
+ case ActivityManager.START_RETURN_INTENT_TO_CALLER:
+ launched = true;
+ out.println(
+ "Warning: Activity not started because intent "
+ + "should be handled by the caller");
+ break;
+ case ActivityManager.START_TASK_TO_FRONT:
+ launched = true;
+ out.println(
+ "Warning: Activity not started, its current "
+ + "task has been brought to the front");
+ break;
+ case ActivityManager.START_INTENT_NOT_RESOLVED:
+ out.println(
+ "Error: Activity not started, unable to "
+ + "resolve " + intent.toString());
+ break;
+ case ActivityManager.START_CLASS_NOT_FOUND:
+ out.println(NO_CLASS_ERROR_CODE);
+ out.println("Error: Activity class " +
+ intent.getComponent().toShortString()
+ + " does not exist.");
+ break;
+ case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+ out.println(
+ "Error: Activity not started, you requested to "
+ + "both forward and receive its result");
+ break;
+ case ActivityManager.START_PERMISSION_DENIED:
+ out.println(
+ "Error: Activity not started, you do not "
+ + "have permission to access it.");
+ break;
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ out.println(
+ "Error: Activity not started, voice control not allowed for: "
+ + intent);
+ break;
+ case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
+ out.println(
+ "Error: Not allowed to start background user activity"
+ + " that shouldn't be displayed for all users.");
+ break;
+ default:
+ out.println(
+ "Error: Activity not started, unknown error code " + res);
+ break;
+ }
+ out.flush();
+ if (mWaitOption && launched) {
+ if (result == null) {
+ result = new IActivityManager.WaitResult();
+ result.who = intent.getComponent();
+ }
+ pw.println("Status: " + (result.timeout ? "timeout" : "ok"));
+ if (result.who != null) {
+ pw.println("Activity: " + result.who.flattenToShortString());
+ }
+ if (result.thisTime >= 0) {
+ pw.println("ThisTime: " + result.thisTime);
+ }
+ if (result.totalTime >= 0) {
+ pw.println("TotalTime: " + result.totalTime);
+ }
+ pw.println("WaitTime: " + (endTime-startTime));
+ pw.println("Complete");
+ pw.flush();
+ }
+ mRepeat--;
+ if (mRepeat > 0) {
+ mInterface.unhandledBack();
+ }
+ } while (mRepeat > 0);
+ return 0;
+ }
+
+ int runStartService(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ if (mUserId == UserHandle.USER_ALL) {
+ err.println("Error: Can't start activity with user 'all'");
+ return -1;
+ }
+ pw.println("Starting service: " + intent);
+ pw.flush();
+ ComponentName cn = mInterface.startService(null, intent, intent.getType(),
+ SHELL_PACKAGE_NAME, mUserId);
+ if (cn == null) {
+ err.println("Error: Not found; no service started.");
+ return -1;
+ } else if (cn.getPackageName().equals("!")) {
+ err.println("Error: Requires permission " + cn.getClassName());
+ return -1;
+ } else if (cn.getPackageName().equals("!!")) {
+ err.println("Error: " + cn.getClassName());
+ return -1;
+ }
+ return 0;
+ }
+
+ int runStopService(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ if (mUserId == UserHandle.USER_ALL) {
+ err.println("Error: Can't stop activity with user 'all'");
+ return -1;
+ }
+ pw.println("Stopping service: " + intent);
+ pw.flush();
+ int result = mInterface.stopService(null, intent, intent.getType(), mUserId);
+ if (result == 0) {
+ err.println("Service not stopped: was not running.");
+ return -1;
+ } else if (result == 1) {
+ err.println("Service stopped");
+ return -1;
+ } else if (result == -1) {
+ err.println("Error stopping service");
+ return -1;
+ }
+ return 0;
+ }
+
+ final static class IntentReceiver extends IIntentReceiver.Stub {
+ private final PrintWriter mPw;
+ private boolean mFinished = false;
+
+ IntentReceiver(PrintWriter pw) {
+ mPw = pw;
+ }
+
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+ boolean ordered, boolean sticky, int sendingUser) {
+ String line = "Broadcast completed: result=" + resultCode;
+ if (data != null) line = line + ", data=\"" + data + "\"";
+ if (extras != null) line = line + ", extras: " + extras;
+ mPw.println(line);
+ mPw.flush();
+ synchronized (this) {
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ public synchronized void waitForFinish() {
+ try {
+ while (!mFinished) wait();
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ int runSendBroadcast(PrintWriter pw) throws RemoteException {
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ IntentReceiver receiver = new IntentReceiver(pw);
+ String[] requiredPermissions = mReceiverPermission == null ? null
+ : new String[] {mReceiverPermission};
+ pw.println("Broadcasting: " + intent);
+ pw.flush();
+ mInterface.broadcastIntent(null, intent, null, receiver, 0, null, null, requiredPermissions,
+ android.app.AppOpsManager.OP_NONE, null, true, false, mUserId);
+ receiver.waitForFinish();
+ return 0;
+ }
+
+ final static class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
+ private final IActivityManager mInterface;
+ private final PrintWriter mPw;
+ private boolean mFinished = false;
+ private boolean mRawMode = false;
+
+ InstrumentationWatcher(IActivityManager iam, PrintWriter pw) {
+ mInterface = iam;
+ mPw = pw;
+ }
+
+ /**
+ * 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) {
+ mPw.print(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ mPw.println(
+ "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
+ }
+ }
+ mPw.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
+ }
+ mPw.flush();
+ 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) {
+ mPw.println(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ mPw.println(
+ "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
+ }
+ }
+ mPw.println("INSTRUMENTATION_CODE: " + resultCode);
+ }
+ mPw.flush();
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ public boolean waitForFinish() {
+ synchronized (this) {
+ while (!mFinished) {
+ try {
+ if (!mInterface.asBinder().pingBinder()) {
+ return false;
+ }
+ wait(1000);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ int runInstrument(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ 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;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("-p")) {
+ profileFile = getNextArgRequired();
+ } else if (opt.equals("-w")) {
+ wait = true;
+ } else if (opt.equals("-r")) {
+ rawMode = true;
+ } else if (opt.equals("-e")) {
+ argKey = getNextArgRequired();
+ argValue = getNextArgRequired();
+ args.putString(argKey, argValue);
+ } else if (opt.equals("--no_window_animation")
+ || opt.equals("--no-window-animation")) {
+ no_window_animation = true;
+ } else if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else if (opt.equals("--abi")) {
+ abi = getNextArgRequired();
+ } else {
+ err.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ err.println("Error: Can't start instrumentation with user 'all'");
+ return -1;
+ }
+
+ String cnArg = getNextArgRequired();
+
+ 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(mInterface, pw);
+ watcher.setRawOutput(rawMode);
+ // Don't yet know how to support this.
+ connection = null; //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 RuntimeException(
+ "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
+ }
+ }
+
+ if (!mInterface.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
+ abi)) {
+ throw new RuntimeException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
+ }
+
+ if (watcher != null) {
+ if (!watcher.waitForFinish()) {
+ pw.println("INSTRUMENTATION_ABORTED: System has crashed.");
+ }
+ }
+
+ if (oldAnims != null) {
+ wm.setAnimationScales(oldAnims);
+ }
+ return 0;
+ }
+
+ int runTraceIpc(PrintWriter pw) throws RemoteException {
+ String op = getNextArgRequired();
+ if (op.equals("start")) {
+ return runTraceIpcStart(pw);
+ } else if (op.equals("stop")) {
+ return runTraceIpcStop(pw);
+ } else {
+ getErrPrintWriter().println("Error: unknown trace ipc command '" + op + "'");
+ return -1;
+ }
+ }
+
+ int runTraceIpcStart(PrintWriter pw) throws RemoteException {
+ pw.println("Starting IPC tracing.");
+ pw.flush();
+ mInterface.startBinderTracking();
+ return 0;
+ }
+
+ int runTraceIpcStop(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ String opt;
+ String filename = null;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--dump-file")) {
+ filename = getNextArgRequired();
+ } else {
+ err.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ if (filename == null) {
+ err.println("Error: Specify filename to dump logs to.");
+ return -1;
+ }
+
+ File file = new File(filename);
+ file.delete();
+ ParcelFileDescriptor fd = openOutputFileForSystem(filename);
+ if (fd == null) {
+ return -1;
+ }
+
+ ;
+ if (!mInterface.stopBinderTrackingAndDump(fd)) {
+ err.println("STOP TRACE FAILED.");
+ return -1;
+ }
+
+ pw.println("Stopped IPC tracing. Dumping logs to: " + filename);
+ return 0;
+ }
+
+ static void removeWallOption() {
+ String props = SystemProperties.get("dalvik.vm.extra-opts");
+ if (props != null && props.contains("-Xprofile:wallclock")) {
+ props = props.replace("-Xprofile:wallclock", "");
+ props = props.trim();
+ SystemProperties.set("dalvik.vm.extra-opts", props);
+ }
+ }
+
+ private int runProfile(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ String profileFile = null;
+ boolean start = false;
+ boolean wall = false;
+ int userId = UserHandle.USER_CURRENT;
+ int profileType = 0;
+ mSamplingInterval = 0;
+
+ String process = null;
+
+ String cmd = getNextArgRequired();
+
+ if ("start".equals(cmd)) {
+ start = true;
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else if (opt.equals("--wall")) {
+ wall = true;
+ } else if (opt.equals("--sampling")) {
+ mSamplingInterval = Integer.parseInt(getNextArgRequired());
+ } else {
+ err.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ process = getNextArgRequired();
+ } else if ("stop".equals(cmd)) {
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ err.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ process = getNextArg();
+ } else {
+ // Compatibility with old syntax: process is specified first.
+ process = cmd;
+ cmd = getNextArgRequired();
+ if ("start".equals(cmd)) {
+ start = true;
+ } else if (!"stop".equals(cmd)) {
+ throw new IllegalArgumentException("Profile command " + process + " not valid");
+ }
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ err.println("Error: Can't profile with user 'all'");
+ return -1;
+ }
+
+ ParcelFileDescriptor fd = null;
+ ProfilerInfo profilerInfo = null;
+
+ if (start) {
+ profileFile = getNextArgRequired();
+ fd = openOutputFileForSystem(profileFile);
+ if (fd == null) {
+ return -1;
+ }
+ profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false);
+ }
+
+ try {
+ if (wall) {
+ // XXX doesn't work -- this needs to be set before booting.
+ String props = SystemProperties.get("dalvik.vm.extra-opts");
+ if (props == null || !props.contains("-Xprofile:wallclock")) {
+ props = props + " -Xprofile:wallclock";
+ //SystemProperties.set("dalvik.vm.extra-opts", props);
+ }
+ } else if (start) {
+ //removeWallOption();
+ }
+ if (!mInterface.profileControl(process, userId, start, profilerInfo, profileType)) {
+ wall = false;
+ err.println("PROFILE FAILED on process " + process);
+ return -1;
+ }
+ } finally {
+ if (!wall) {
+ //removeWallOption();
+ }
+ }
+ return 0;
+ }
+
+ int runDumpHeap(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ boolean managed = true;
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId == UserHandle.USER_ALL) {
+ err.println("Error: Can't dump heap with user 'all'");
+ return -1;
+ }
+ } else if (opt.equals("-n")) {
+ managed = false;
+ } else {
+ err.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ String process = getNextArgRequired();
+ String heapFile = getNextArgRequired();
+
+ File file = new File(heapFile);
+ file.delete();
+ ParcelFileDescriptor fd = openOutputFileForSystem(heapFile);
+ if (fd == null) {
+ return -1;
+ }
+
+ if (!mInterface.dumpHeap(process, userId, managed, heapFile, fd)) {
+ err.println("HEAP DUMP FAILED on process " + process);
+ return -1;
+ }
+ return 0;
+ }
+
+ int runSetDebugApp(PrintWriter pw) throws RemoteException {
+ boolean wait = false;
+ boolean persistent = false;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("-w")) {
+ wait = true;
+ } else if (opt.equals("--persistent")) {
+ persistent = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ String pkg = getNextArgRequired();
+ mInterface.setDebugApp(pkg, wait, persistent);
+ return 0;
+ }
+
+ int runClearDebugApp(PrintWriter pw) throws RemoteException {
+ mInterface.setDebugApp(null, false, true);
+ return 0;
+ }
+
+ int runSetWatchHeap(PrintWriter pw) throws RemoteException {
+ String proc = getNextArgRequired();
+ String limit = getNextArgRequired();
+ mInterface.setDumpHeapDebugLimit(proc, 0, Long.parseLong(limit), null);
+ return 0;
+ }
+
+ int runClearWatchHeap(PrintWriter pw) throws RemoteException {
+ String proc = getNextArgRequired();
+ mInterface.setDumpHeapDebugLimit(proc, 0, -1, null);
+ return 0;
+ }
+
+ int runBugReport(PrintWriter pw) throws RemoteException {
+ String opt;
+ int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--progress")) {
+ bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ mInterface.requestBugReport(bugreportType);
+ pw.println("Your lovely bug report is being created; please be patient.");
return 0;
}
@@ -90,7 +1033,7 @@
if (opt.equals("--user")) {
userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
- pw.println("Error: Unknown option: " + opt);
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
}
}
@@ -106,7 +1049,7 @@
if (opt.equals("--user")) {
userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
- pw.println("Error: Unknown option: " + opt);
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
}
}
@@ -119,11 +1062,553 @@
return 0;
}
- int runWrite(PrintWriter pw) {
- mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
- "registerUidObserver()");
- mInternal.mRecentTasks.flush();
- pw.println("All tasks persisted.");
+ static final class MyActivityController extends IActivityController.Stub {
+ final IActivityManager mInterface;
+ final PrintWriter mPw;
+ final InputStream mInput;
+ final String mGdbPort;
+ final boolean mMonkey;
+
+ static final int STATE_NORMAL = 0;
+ static final int STATE_CRASHED = 1;
+ static final int STATE_EARLY_ANR = 2;
+ static final int STATE_ANR = 3;
+
+ int mState;
+
+ static final int RESULT_DEFAULT = 0;
+
+ static final int RESULT_CRASH_DIALOG = 0;
+ static final int RESULT_CRASH_KILL = 1;
+
+ static final int RESULT_EARLY_ANR_CONTINUE = 0;
+ static final int RESULT_EARLY_ANR_KILL = 1;
+
+ static final int RESULT_ANR_DIALOG = 0;
+ static final int RESULT_ANR_KILL = 1;
+ static final int RESULT_ANR_WAIT = 1;
+
+ int mResult;
+
+ Process mGdbProcess;
+ Thread mGdbThread;
+ boolean mGotGdbPrint;
+
+ MyActivityController(IActivityManager iam, PrintWriter pw, InputStream input,
+ String gdbPort, boolean monkey) {
+ mInterface = iam;
+ mPw = pw;
+ mInput = input;
+ mGdbPort = gdbPort;
+ mMonkey = monkey;
+ }
+
+ @Override
+ public boolean activityResuming(String pkg) {
+ synchronized (this) {
+ mPw.println("** Activity resuming: " + pkg);
+ mPw.flush();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean activityStarting(Intent intent, String pkg) {
+ synchronized (this) {
+ mPw.println("** Activity starting: " + pkg);
+ mPw.flush();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
+ long timeMillis, String stackTrace) {
+ synchronized (this) {
+ mPw.println("** ERROR: PROCESS CRASHED");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("shortMsg: " + shortMsg);
+ mPw.println("longMsg: " + longMsg);
+ mPw.println("timeMillis: " + timeMillis);
+ mPw.println("stack:");
+ mPw.print(stackTrace);
+ mPw.println("#");
+ mPw.flush();
+ int result = waitControllerLocked(pid, STATE_CRASHED);
+ return result == RESULT_CRASH_KILL ? false : true;
+ }
+ }
+
+ @Override
+ public int appEarlyNotResponding(String processName, int pid, String annotation) {
+ synchronized (this) {
+ mPw.println("** ERROR: EARLY PROCESS NOT RESPONDING");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("annotation: " + annotation);
+ mPw.flush();
+ int result = waitControllerLocked(pid, STATE_EARLY_ANR);
+ if (result == RESULT_EARLY_ANR_KILL) return -1;
+ return 0;
+ }
+ }
+
+ @Override
+ public int appNotResponding(String processName, int pid, String processStats) {
+ synchronized (this) {
+ mPw.println("** ERROR: PROCESS NOT RESPONDING");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("processStats:");
+ mPw.print(processStats);
+ mPw.println("#");
+ mPw.flush();
+ int result = waitControllerLocked(pid, STATE_ANR);
+ if (result == RESULT_ANR_KILL) return -1;
+ if (result == RESULT_ANR_WAIT) return 1;
+ return 0;
+ }
+ }
+
+ @Override
+ public int systemNotResponding(String message) {
+ synchronized (this) {
+ mPw.println("** ERROR: PROCESS NOT RESPONDING");
+ mPw.println("message: " + message);
+ mPw.println("#");
+ mPw.println("Allowing system to die.");
+ mPw.flush();
+ return -1;
+ }
+ }
+
+ void killGdbLocked() {
+ mGotGdbPrint = false;
+ if (mGdbProcess != null) {
+ mPw.println("Stopping gdbserver");
+ mPw.flush();
+ mGdbProcess.destroy();
+ mGdbProcess = null;
+ }
+ if (mGdbThread != null) {
+ mGdbThread.interrupt();
+ mGdbThread = null;
+ }
+ }
+
+ int waitControllerLocked(int pid, int state) {
+ if (mGdbPort != null) {
+ killGdbLocked();
+
+ try {
+ mPw.println("Starting gdbserver on port " + mGdbPort);
+ mPw.println("Do the following:");
+ mPw.println(" adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort);
+ mPw.println(" gdbclient app_process :" + mGdbPort);
+ mPw.flush();
+
+ mGdbProcess = Runtime.getRuntime().exec(new String[] {
+ "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid)
+ });
+ final InputStreamReader converter = new InputStreamReader(
+ mGdbProcess.getInputStream());
+ mGdbThread = new Thread() {
+ @Override
+ public void run() {
+ BufferedReader in = new BufferedReader(converter);
+ String line;
+ int count = 0;
+ while (true) {
+ synchronized (MyActivityController.this) {
+ if (mGdbThread == null) {
+ return;
+ }
+ if (count == 2) {
+ mGotGdbPrint = true;
+ MyActivityController.this.notifyAll();
+ }
+ }
+ try {
+ line = in.readLine();
+ if (line == null) {
+ return;
+ }
+ mPw.println("GDB: " + line);
+ mPw.flush();
+ count++;
+ } catch (IOException e) {
+ return;
+ }
+ }
+ }
+ };
+ mGdbThread.start();
+
+ // Stupid waiting for .5s. Doesn't matter if we end early.
+ try {
+ this.wait(500);
+ } catch (InterruptedException e) {
+ }
+
+ } catch (IOException e) {
+ mPw.println("Failure starting gdbserver: " + e);
+ mPw.flush();
+ killGdbLocked();
+ }
+ }
+ mState = state;
+ mPw.println("");
+ printMessageForState();
+ mPw.flush();
+
+ while (mState != STATE_NORMAL) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ killGdbLocked();
+
+ return mResult;
+ }
+
+ void resumeController(int result) {
+ synchronized (this) {
+ mState = STATE_NORMAL;
+ mResult = result;
+ notifyAll();
+ }
+ }
+
+ void printMessageForState() {
+ switch (mState) {
+ case STATE_NORMAL:
+ mPw.println("Monitoring activity manager... available commands:");
+ break;
+ case STATE_CRASHED:
+ mPw.println("Waiting after crash... available commands:");
+ mPw.println("(c)ontinue: show crash dialog");
+ mPw.println("(k)ill: immediately kill app");
+ break;
+ case STATE_EARLY_ANR:
+ mPw.println("Waiting after early ANR... available commands:");
+ mPw.println("(c)ontinue: standard ANR processing");
+ mPw.println("(k)ill: immediately kill app");
+ break;
+ case STATE_ANR:
+ mPw.println("Waiting after ANR... available commands:");
+ mPw.println("(c)ontinue: show ANR dialog");
+ mPw.println("(k)ill: immediately kill app");
+ mPw.println("(w)ait: wait some more");
+ break;
+ }
+ mPw.println("(q)uit: finish monitoring");
+ }
+
+ void run() throws RemoteException {
+ try {
+ printMessageForState();
+ mPw.flush();
+
+ mInterface.setActivityController(this, mMonkey);
+ mState = STATE_NORMAL;
+
+ InputStreamReader converter = new InputStreamReader(mInput);
+ BufferedReader in = new BufferedReader(converter);
+ String line;
+
+ while ((line = in.readLine()) != null) {
+ boolean addNewline = true;
+ if (line.length() <= 0) {
+ addNewline = false;
+ } else if ("q".equals(line) || "quit".equals(line)) {
+ resumeController(RESULT_DEFAULT);
+ break;
+ } else if (mState == STATE_CRASHED) {
+ if ("c".equals(line) || "continue".equals(line)) {
+ resumeController(RESULT_CRASH_DIALOG);
+ } else if ("k".equals(line) || "kill".equals(line)) {
+ resumeController(RESULT_CRASH_KILL);
+ } else {
+ mPw.println("Invalid command: " + line);
+ }
+ } else if (mState == STATE_ANR) {
+ if ("c".equals(line) || "continue".equals(line)) {
+ resumeController(RESULT_ANR_DIALOG);
+ } else if ("k".equals(line) || "kill".equals(line)) {
+ resumeController(RESULT_ANR_KILL);
+ } else if ("w".equals(line) || "wait".equals(line)) {
+ resumeController(RESULT_ANR_WAIT);
+ } else {
+ mPw.println("Invalid command: " + line);
+ }
+ } else if (mState == STATE_EARLY_ANR) {
+ if ("c".equals(line) || "continue".equals(line)) {
+ resumeController(RESULT_EARLY_ANR_CONTINUE);
+ } else if ("k".equals(line) || "kill".equals(line)) {
+ resumeController(RESULT_EARLY_ANR_KILL);
+ } else {
+ mPw.println("Invalid command: " + line);
+ }
+ } else {
+ mPw.println("Invalid command: " + line);
+ }
+
+ synchronized (this) {
+ if (addNewline) {
+ mPw.println("");
+ }
+ printMessageForState();
+ mPw.flush();
+ }
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace(mPw);
+ mPw.flush();
+ } finally {
+ mInterface.setActivityController(null, mMonkey);
+ }
+ }
+ }
+
+ int runMonitor(PrintWriter pw) throws RemoteException {
+ String opt;
+ String gdbPort = null;
+ boolean monkey = false;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--gdb")) {
+ gdbPort = getNextArgRequired();
+ } else if (opt.equals("-m")) {
+ monkey = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ MyActivityController controller = new MyActivityController(mInterface, pw,
+ getRawInputStream(), gdbPort, monkey);
+ controller.run();
+ return 0;
+ }
+
+ int runHang(PrintWriter pw) throws RemoteException {
+ String opt;
+ boolean allowRestart = false;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--allow-restart")) {
+ allowRestart = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ pw.println("Hanging the system...");
+ pw.flush();
+ mInterface.hang(new Binder(), allowRestart);
+ return 0;
+ }
+
+ int runRestart(PrintWriter pw) throws RemoteException {
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+
+ pw.println("Restart the system...");
+ pw.flush();
+ mInterface.restart();
+ return 0;
+ }
+
+ int runIdleMaintenance(PrintWriter pw) throws RemoteException {
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+
+ pw.println("Performing idle maintenance...");
+ mInterface.sendIdleJobTrigger();
+ return 0;
+ }
+
+ int runScreenCompat(PrintWriter pw) throws RemoteException {
+ String mode = getNextArgRequired();
+ boolean enabled;
+ if ("on".equals(mode)) {
+ enabled = true;
+ } else if ("off".equals(mode)) {
+ enabled = false;
+ } else {
+ getErrPrintWriter().println("Error: enabled mode must be 'on' or 'off' at " + mode);
+ return -1;
+ }
+
+ String packageName = getNextArgRequired();
+ do {
+ try {
+ mInterface.setPackageScreenCompatMode(packageName, enabled
+ ? ActivityManager.COMPAT_MODE_ENABLED
+ : ActivityManager.COMPAT_MODE_DISABLED);
+ } catch (RemoteException e) {
+ }
+ packageName = getNextArg();
+ } while (packageName != null);
+ return 0;
+ }
+
+ int runPackageImportance(PrintWriter pw) throws RemoteException {
+ String packageName = getNextArgRequired();
+ int procState = mInterface.getPackageProcessState(packageName, "com.android.shell");
+ pw.println(ActivityManager.RunningAppProcessInfo.procStateToImportance(procState));
+ return 0;
+ }
+
+ int runToUri(PrintWriter pw, int flags) throws RemoteException {
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ pw.println(intent.toUri(flags));
+ return 0;
+ }
+
+ int runSwitchUser(PrintWriter pw) throws RemoteException {
+ String user = getNextArgRequired();
+ mInterface.switchUser(Integer.parseInt(user));
+ return 0;
+ }
+
+ int runGetCurrentUser(PrintWriter pw) throws RemoteException {
+ UserInfo currentUser = Preconditions.checkNotNull(mInterface.getCurrentUser(),
+ "Current user not set");
+ pw.println(currentUser.id);
+ return 0;
+ }
+
+ int runStartUser(PrintWriter pw) throws RemoteException {
+ String user = getNextArgRequired();
+ boolean success = mInterface.startUserInBackground(Integer.parseInt(user));
+ if (success) {
+ pw.println("Success: user started");
+ } else {
+ getErrPrintWriter().println("Error: could not start user");
+ }
+ return 0;
+ }
+
+ private static byte[] argToBytes(String arg) {
+ if (arg.equals("!")) {
+ return null;
+ } else {
+ return HexDump.hexStringToByteArray(arg);
+ }
+ }
+
+ int runUnlockUser(PrintWriter pw) throws RemoteException {
+ int userId = Integer.parseInt(getNextArgRequired());
+ byte[] token = argToBytes(getNextArgRequired());
+ byte[] secret = argToBytes(getNextArgRequired());
+ boolean success = mInterface.unlockUser(userId, token, secret, null);
+ if (success) {
+ pw.println("Success: user unlocked");
+ } else {
+ getErrPrintWriter().println("Error: could not unlock user");
+ }
+ return 0;
+ }
+
+ static final class StopUserCallback extends IStopUserCallback.Stub {
+ private boolean mFinished = false;
+
+ public synchronized void waitForFinish() {
+ try {
+ while (!mFinished) wait();
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public synchronized void userStopped(int userId) {
+ mFinished = true;
+ notifyAll();
+ }
+
+ @Override
+ public synchronized void userStopAborted(int userId) {
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ int runStopUser(PrintWriter pw) throws RemoteException {
+ boolean wait = false;
+ boolean force = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if ("-w".equals(opt)) {
+ wait = true;
+ } else if ("-f".equals(opt)) {
+ force = true;
+ } else {
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return -1;
+ }
+ }
+ int user = Integer.parseInt(getNextArgRequired());
+ StopUserCallback callback = wait ? new StopUserCallback() : null;
+
+ int res = mInterface.stopUser(user, force, callback);
+ if (res != ActivityManager.USER_OP_SUCCESS) {
+ String txt = "";
+ switch (res) {
+ case ActivityManager.USER_OP_IS_CURRENT:
+ txt = " (Can't stop current user)";
+ break;
+ case ActivityManager.USER_OP_UNKNOWN_USER:
+ txt = " (Unknown user " + user + ")";
+ break;
+ case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
+ txt = " (System user cannot be stopped)";
+ break;
+ case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP:
+ txt = " (Can't stop user " + user
+ + " - one of its related users can't be stopped)";
+ break;
+ }
+ getErrPrintWriter().println("Switch failed: " + res + txt);
+ return -1;
+ } else if (callback != null) {
+ callback.waitForFinish();
+ }
+ return 0;
+ }
+
+ int runIsUserStopped(PrintWriter pw) {
+ int userId = UserHandle.parseUserArg(getNextArgRequired());
+ boolean stopped = mInternal.isUserStopped(userId);
+ pw.println(stopped);
+ return 0;
+ }
+
+ int runGetStartedUserState(PrintWriter pw) throws RemoteException {
+ mInternal.enforceCallingPermission(android.Manifest.permission.DUMP,
+ "runGetStartedUserState()");
+ final int userId = Integer.parseInt(getNextArgRequired());
+ try {
+ pw.println(mInternal.getStartedUserState(userId));
+ } catch (NullPointerException e) {
+ pw.println("User is not started: " + userId);
+ }
return 0;
}
@@ -183,6 +1668,817 @@
return 0;
}
+ private List<Configuration> getRecentConfigurations(int days) {
+ IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+ Context.USAGE_STATS_SERVICE));
+ final long now = System.currentTimeMillis();
+ final long nDaysAgo = now - (days * 24 * 60 * 60 * 1000);
+ try {
+ @SuppressWarnings("unchecked")
+ ParceledListSlice<ConfigurationStats> configStatsSlice = usm.queryConfigurationStats(
+ UsageStatsManager.INTERVAL_BEST, nDaysAgo, now, "com.android.shell");
+ if (configStatsSlice == null) {
+ return Collections.emptyList();
+ }
+
+ final ArrayMap<Configuration, Integer> recentConfigs = new ArrayMap<>();
+ final List<ConfigurationStats> configStatsList = configStatsSlice.getList();
+ final int configStatsListSize = configStatsList.size();
+ for (int i = 0; i < configStatsListSize; i++) {
+ final ConfigurationStats stats = configStatsList.get(i);
+ final int indexOfKey = recentConfigs.indexOfKey(stats.getConfiguration());
+ if (indexOfKey < 0) {
+ recentConfigs.put(stats.getConfiguration(), stats.getActivationCount());
+ } else {
+ recentConfigs.setValueAt(indexOfKey,
+ recentConfigs.valueAt(indexOfKey) + stats.getActivationCount());
+ }
+ }
+
+ final Comparator<Configuration> comparator = new Comparator<Configuration>() {
+ @Override
+ public int compare(Configuration a, Configuration b) {
+ return recentConfigs.get(b).compareTo(recentConfigs.get(a));
+ }
+ };
+
+ ArrayList<Configuration> configs = new ArrayList<>(recentConfigs.size());
+ configs.addAll(recentConfigs.keySet());
+ Collections.sort(configs, comparator);
+ return configs;
+
+ } catch (RemoteException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ int runGetConfig(PrintWriter pw) throws RemoteException {
+ int days = 14;
+ String option = getNextOption();
+ if (option != null) {
+ if (!option.equals("--days")) {
+ throw new IllegalArgumentException("unrecognized option " + option);
+ }
+
+ days = Integer.parseInt(getNextArgRequired());
+ if (days <= 0) {
+ throw new IllegalArgumentException("--days must be a positive integer");
+ }
+ }
+
+ Configuration config = mInterface.getConfiguration();
+ if (config == null) {
+ getErrPrintWriter().println("Activity manager has no configuration");
+ return -1;
+ }
+
+ pw.println("config: " + Configuration.resourceQualifierString(config));
+ pw.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS));
+
+ final List<Configuration> recentConfigs = getRecentConfigurations(days);
+ final int recentConfigSize = recentConfigs.size();
+ if (recentConfigSize > 0) {
+ pw.println("recentConfigs:");
+ }
+
+ for (int i = 0; i < recentConfigSize; i++) {
+ pw.println(" config: " + Configuration.resourceQualifierString(
+ recentConfigs.get(i)));
+ }
+ return 0;
+ }
+
+ int runSuppressResizeConfigChanges(PrintWriter pw) throws RemoteException {
+ boolean suppress = Boolean.valueOf(getNextArgRequired());
+ mInterface.suppressResizeConfigChanges(suppress);
+ return 0;
+ }
+
+ int runSetInactive(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ String packageName = getNextArgRequired();
+ String value = getNextArgRequired();
+
+ IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+ Context.USAGE_STATS_SERVICE));
+ usm.setAppInactive(packageName, Boolean.parseBoolean(value), userId);
+ return 0;
+ }
+
+ int runGetInactive(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ String packageName = getNextArgRequired();
+
+ IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+ Context.USAGE_STATS_SERVICE));
+ boolean isIdle = usm.isAppInactive(packageName, userId);
+ pw.println("Idle=" + isIdle);
+ return 0;
+ }
+
+ int runSendTrimMemory(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId == UserHandle.USER_ALL) {
+ getErrPrintWriter().println("Error: Can't use user 'all'");
+ return -1;
+ }
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ String proc = getNextArgRequired();
+ String levelArg = getNextArgRequired();
+ int level;
+ switch (levelArg) {
+ case "HIDDEN":
+ level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
+ break;
+ case "RUNNING_MODERATE":
+ level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
+ break;
+ case "BACKGROUND":
+ level = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+ break;
+ case "RUNNING_LOW":
+ level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+ break;
+ case "MODERATE":
+ level = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
+ break;
+ case "RUNNING_CRITICAL":
+ level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
+ break;
+ case "COMPLETE":
+ level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
+ break;
+ default:
+ getErrPrintWriter().println("Error: Unknown level option: " + levelArg);
+ return -1;
+ }
+ if (!mInterface.setProcessMemoryTrimLevel(proc, userId, level)) {
+ getErrPrintWriter().println("Unknown error: failed to set trim level");
+ return -1;
+ }
+ return 0;
+ }
+
+ int runStack(PrintWriter pw) throws RemoteException {
+ String op = getNextArgRequired();
+ switch (op) {
+ case "start":
+ return runStackStart(pw);
+ case "movetask":
+ return runStackMoveTask(pw);
+ case "resize":
+ return runStackResize(pw);
+ case "resize-animated":
+ return runStackResizeAnimated(pw);
+ case "resize-docked-stack":
+ return runStackResizeDocked(pw);
+ case "positiontask":
+ return runStackPositionTask(pw);
+ case "list":
+ return runStackList(pw);
+ case "info":
+ return runStackInfo(pw);
+ case "move-top-activity-to-pinned-stack":
+ return runMoveTopActivityToPinnedStack(pw);
+ case "size-docked-stack-test":
+ return runStackSizeDockedStackTest(pw);
+ case "remove":
+ return runStackRemove(pw);
+ default:
+ getErrPrintWriter().println("Error: unknown command '" + op + "'");
+ return -1;
+ }
+ }
+
+
+ private Rect getBounds() {
+ String leftStr = getNextArgRequired();
+ int left = Integer.parseInt(leftStr);
+ String topStr = getNextArgRequired();
+ int top = Integer.parseInt(topStr);
+ String rightStr = getNextArgRequired();
+ int right = Integer.parseInt(rightStr);
+ String bottomStr = getNextArgRequired();
+ int bottom = Integer.parseInt(bottomStr);
+ if (left < 0) {
+ getErrPrintWriter().println("Error: bad left arg: " + leftStr);
+ return null;
+ }
+ if (top < 0) {
+ getErrPrintWriter().println("Error: bad top arg: " + topStr);
+ return null;
+ }
+ if (right <= 0) {
+ getErrPrintWriter().println("Error: bad right arg: " + rightStr);
+ return null;
+ }
+ if (bottom <= 0) {
+ getErrPrintWriter().println("Error: bad bottom arg: " + bottomStr);
+ return null;
+ }
+ return new Rect(left, top, right, bottom);
+ }
+
+ int runStackStart(PrintWriter pw) throws RemoteException {
+ String displayIdStr = getNextArgRequired();
+ int displayId = Integer.parseInt(displayIdStr);
+ Intent intent;
+ try {
+ intent = makeIntent(UserHandle.USER_CURRENT);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+
+ IActivityContainer container = mInterface.createStackOnDisplay(displayId);
+ if (container != null) {
+ container.startActivity(intent);
+ }
+ return 0;
+ }
+
+ int runStackMoveTask(PrintWriter pw) throws RemoteException {
+ String taskIdStr = getNextArgRequired();
+ int taskId = Integer.parseInt(taskIdStr);
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ String toTopStr = getNextArgRequired();
+ final boolean toTop;
+ if ("true".equals(toTopStr)) {
+ toTop = true;
+ } else if ("false".equals(toTopStr)) {
+ toTop = false;
+ } else {
+ getErrPrintWriter().println("Error: bad toTop arg: " + toTopStr);
+ return -1;
+ }
+
+ mInterface.moveTaskToStack(taskId, stackId, toTop);
+ return 0;
+ }
+
+ int runStackResize(PrintWriter pw) throws RemoteException {
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ final Rect bounds = getBounds();
+ if (bounds == null) {
+ getErrPrintWriter().println("Error: invalid input bounds");
+ return -1;
+ }
+ return resizeStack(stackId, bounds, 0);
+ }
+
+ int runStackResizeAnimated(PrintWriter pw) throws RemoteException {
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ final Rect bounds;
+ if ("null".equals(peekNextArg())) {
+ bounds = null;
+ } else {
+ bounds = getBounds();
+ if (bounds == null) {
+ getErrPrintWriter().println("Error: invalid input bounds");
+ return -1;
+ }
+ }
+ return resizeStackUnchecked(stackId, bounds, 0, true);
+ }
+
+ int resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate)
+ throws RemoteException {
+ try {
+ mInterface.resizeStack(stackId, bounds, false, false, animate, -1);
+ Thread.sleep(delayMs);
+ } catch (InterruptedException e) {
+ }
+ return 0;
+ }
+
+ int runStackResizeDocked(PrintWriter pw) throws RemoteException {
+ final Rect bounds = getBounds();
+ final Rect taskBounds = getBounds();
+ if (bounds == null || taskBounds == null) {
+ getErrPrintWriter().println("Error: invalid input bounds");
+ return -1;
+ }
+ mInterface.resizeDockedStack(bounds, taskBounds, null, null, null);
+ return 0;
+ }
+
+ int resizeStack(int stackId, Rect bounds, int delayMs) throws RemoteException {
+ if (bounds == null) {
+ getErrPrintWriter().println("Error: invalid input bounds");
+ return -1;
+ }
+ return resizeStackUnchecked(stackId, bounds, delayMs, false);
+ }
+
+ int runStackPositionTask(PrintWriter pw) throws RemoteException {
+ String taskIdStr = getNextArgRequired();
+ int taskId = Integer.parseInt(taskIdStr);
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ String positionStr = getNextArgRequired();
+ int position = Integer.parseInt(positionStr);
+
+ mInterface.positionTaskInStack(taskId, stackId, position);
+ return 0;
+ }
+
+ int runStackList(PrintWriter pw) throws RemoteException {
+ List<ActivityManager.StackInfo> stacks = mInterface.getAllStackInfos();
+ for (ActivityManager.StackInfo info : stacks) {
+ pw.println(info);
+ }
+ return 0;
+ }
+
+ int runStackInfo(PrintWriter pw) throws RemoteException {
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ ActivityManager.StackInfo info = mInterface.getStackInfo(stackId);
+ pw.println(info);
+ return 0;
+ }
+
+ int runStackRemove(PrintWriter pw) throws RemoteException {
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ mInterface.removeStack(stackId);
+ return 0;
+ }
+
+ int runMoveTopActivityToPinnedStack(PrintWriter pw) throws RemoteException {
+ int stackId = Integer.parseInt(getNextArgRequired());
+ final Rect bounds = getBounds();
+ if (bounds == null) {
+ getErrPrintWriter().println("Error: invalid input bounds");
+ return -1;
+ }
+
+ if (!mInterface.moveTopActivityToPinnedStack(stackId, bounds)) {
+ getErrPrintWriter().println("Didn't move top activity to pinned stack.");
+ return -1;
+ }
+ return 0;
+ }
+
+ int runStackSizeDockedStackTest(PrintWriter pw) throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ final int stepSize = Integer.parseInt(getNextArgRequired());
+ final String side = getNextArgRequired();
+ final String delayStr = getNextArg();
+ final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
+
+ ActivityManager.StackInfo info = mInterface.getStackInfo(DOCKED_STACK_ID);
+ if (info == null) {
+ err.println("Docked stack doesn't exist");
+ return -1;
+ }
+ if (info.bounds == null) {
+ err.println("Docked stack doesn't have a bounds");
+ return -1;
+ }
+ Rect bounds = info.bounds;
+
+ final boolean horizontalGrowth = "l".equals(side) || "r".equals(side);
+ final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2;
+ int currentPoint;
+ switch (side) {
+ case "l":
+ currentPoint = bounds.left;
+ break;
+ case "r":
+ currentPoint = bounds.right;
+ break;
+ case "t":
+ currentPoint = bounds.top;
+ break;
+ case "b":
+ currentPoint = bounds.bottom;
+ break;
+ default:
+ err.println("Unknown growth side: " + side);
+ return -1;
+ }
+
+ final int startPoint = currentPoint;
+ final int minPoint = currentPoint - changeSize;
+ final int maxPoint = currentPoint + changeSize;
+
+ int maxChange;
+ pw.println("Shrinking docked stack side=" + side);
+ pw.flush();
+ while (currentPoint > minPoint) {
+ maxChange = Math.min(stepSize, currentPoint - minPoint);
+ currentPoint -= maxChange;
+ setBoundsSide(bounds, side, currentPoint);
+ int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
+ if (res < 0) {
+ return res;
+ }
+ }
+
+ pw.println("Growing docked stack side=" + side);
+ pw.flush();
+ while (currentPoint < maxPoint) {
+ maxChange = Math.min(stepSize, maxPoint - currentPoint);
+ currentPoint += maxChange;
+ setBoundsSide(bounds, side, currentPoint);
+ int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
+ if (res < 0) {
+ return res;
+ }
+ }
+
+ pw.println("Back to Original size side=" + side);
+ pw.flush();
+ while (currentPoint > startPoint) {
+ maxChange = Math.min(stepSize, currentPoint - startPoint);
+ currentPoint -= maxChange;
+ setBoundsSide(bounds, side, currentPoint);
+ int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
+ if (res < 0) {
+ return res;
+ }
+ }
+ return 0;
+ }
+
+ void setBoundsSide(Rect bounds, String side, int value) {
+ switch (side) {
+ case "l":
+ bounds.left = value;
+ break;
+ case "r":
+ bounds.right = value;
+ break;
+ case "t":
+ bounds.top = value;
+ break;
+ case "b":
+ bounds.bottom = value;
+ break;
+ default:
+ getErrPrintWriter().println("Unknown set side: " + side);
+ break;
+ }
+ }
+
+ int runTask(PrintWriter pw) throws RemoteException {
+ String op = getNextArgRequired();
+ if (op.equals("lock")) {
+ return runTaskLock(pw);
+ } else if (op.equals("resizeable")) {
+ return runTaskResizeable(pw);
+ } else if (op.equals("resize")) {
+ return runTaskResize(pw);
+ } else if (op.equals("drag-task-test")) {
+ return runTaskDragTaskTest(pw);
+ } else if (op.equals("size-task-test")) {
+ return runTaskSizeTaskTest(pw);
+ } else {
+ getErrPrintWriter().println("Error: unknown command '" + op + "'");
+ return -1;
+ }
+ }
+
+ int runTaskLock(PrintWriter pw) throws RemoteException {
+ String taskIdStr = getNextArgRequired();
+ if (taskIdStr.equals("stop")) {
+ mInterface.stopLockTaskMode();
+ } else {
+ int taskId = Integer.parseInt(taskIdStr);
+ mInterface.startLockTaskMode(taskId);
+ }
+ pw.println("Activity manager is " + (mInterface.isInLockTaskMode() ? "" : "not ") +
+ "in lockTaskMode");
+ return 0;
+ }
+
+ int runTaskResizeable(PrintWriter pw) throws RemoteException {
+ final String taskIdStr = getNextArgRequired();
+ final int taskId = Integer.parseInt(taskIdStr);
+ final String resizeableStr = getNextArgRequired();
+ final int resizeableMode = Integer.parseInt(resizeableStr);
+ mInterface.setTaskResizeable(taskId, resizeableMode);
+ return 0;
+ }
+
+ int runTaskResize(PrintWriter pw) throws RemoteException {
+ final String taskIdStr = getNextArgRequired();
+ final int taskId = Integer.parseInt(taskIdStr);
+ final Rect bounds = getBounds();
+ if (bounds == null) {
+ getErrPrintWriter().println("Error: invalid input bounds");
+ return -1;
+ }
+ taskResize(taskId, bounds, 0, false);
+ return 0;
+ }
+
+ void taskResize(int taskId, Rect bounds, int delay_ms, boolean pretendUserResize)
+ throws RemoteException {
+ final int resizeMode = pretendUserResize ? RESIZE_MODE_USER : RESIZE_MODE_SYSTEM;
+ mInterface.resizeTask(taskId, bounds, resizeMode);
+ try {
+ Thread.sleep(delay_ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ int runTaskDragTaskTest(PrintWriter pw) throws RemoteException {
+ final int taskId = Integer.parseInt(getNextArgRequired());
+ final int stepSize = Integer.parseInt(getNextArgRequired());
+ final String delayStr = getNextArg();
+ final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
+ final ActivityManager.StackInfo stackInfo;
+ Rect taskBounds;
+ stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
+ taskBounds = mInterface.getTaskBounds(taskId);
+ final Rect stackBounds = stackInfo.bounds;
+ int travelRight = stackBounds.width() - taskBounds.width();
+ int travelLeft = -travelRight;
+ int travelDown = stackBounds.height() - taskBounds.height();
+ int travelUp = -travelDown;
+ int passes = 0;
+
+ // We do 2 passes to get back to the original location of the task.
+ while (passes < 2) {
+ // Move right
+ pw.println("Moving right...");
+ pw.flush();
+ travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize,
+ travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
+ pw.println("Still need to travel right by " + travelRight);
+
+ // Move down
+ pw.println("Moving down...");
+ pw.flush();
+ travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize,
+ travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
+ pw.println("Still need to travel down by " + travelDown);
+
+ // Move left
+ pw.println("Moving left...");
+ pw.flush();
+ travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize,
+ travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
+ pw.println("Still need to travel left by " + travelLeft);
+
+ // Move up
+ pw.println("Moving up...");
+ pw.flush();
+ travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize,
+ travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
+ pw.println("Still need to travel up by " + travelUp);
+
+ taskBounds = mInterface.getTaskBounds(taskId);
+ passes++;
+ }
+ return 0;
+ }
+
+ int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize,
+ int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms)
+ throws RemoteException {
+ int maxMove;
+ if (movingForward) {
+ while (maxToTravel > 0
+ && ((horizontal && taskRect.right < stackRect.right)
+ ||(!horizontal && taskRect.bottom < stackRect.bottom))) {
+ if (horizontal) {
+ maxMove = Math.min(stepSize, stackRect.right - taskRect.right);
+ maxToTravel -= maxMove;
+ taskRect.right += maxMove;
+ taskRect.left += maxMove;
+ } else {
+ maxMove = Math.min(stepSize, stackRect.bottom - taskRect.bottom);
+ maxToTravel -= maxMove;
+ taskRect.top += maxMove;
+ taskRect.bottom += maxMove;
+ }
+ taskResize(taskId, taskRect, delay_ms, false);
+ }
+ } else {
+ while (maxToTravel < 0
+ && ((horizontal && taskRect.left > stackRect.left)
+ ||(!horizontal && taskRect.top > stackRect.top))) {
+ if (horizontal) {
+ maxMove = Math.min(stepSize, taskRect.left - stackRect.left);
+ maxToTravel -= maxMove;
+ taskRect.right -= maxMove;
+ taskRect.left -= maxMove;
+ } else {
+ maxMove = Math.min(stepSize, taskRect.top - stackRect.top);
+ maxToTravel -= maxMove;
+ taskRect.top -= maxMove;
+ taskRect.bottom -= maxMove;
+ }
+ taskResize(taskId, taskRect, delay_ms, false);
+ }
+ }
+ // Return the remaining distance we didn't travel because we reached the target location.
+ return maxToTravel;
+ }
+
+ int getStepSize(int current, int target, int inStepSize, boolean greaterThanTarget) {
+ int stepSize = 0;
+ if (greaterThanTarget && target < current) {
+ current -= inStepSize;
+ stepSize = inStepSize;
+ if (target > current) {
+ stepSize -= (target - current);
+ }
+ }
+ if (!greaterThanTarget && target > current) {
+ current += inStepSize;
+ stepSize = inStepSize;
+ if (target < current) {
+ stepSize += (current - target);
+ }
+ }
+ return stepSize;
+ }
+
+ int runTaskSizeTaskTest(PrintWriter pw) throws RemoteException {
+ final int taskId = Integer.parseInt(getNextArgRequired());
+ final int stepSize = Integer.parseInt(getNextArgRequired());
+ final String delayStr = getNextArg();
+ final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
+ final ActivityManager.StackInfo stackInfo;
+ final Rect initialTaskBounds;
+ stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
+ initialTaskBounds = mInterface.getTaskBounds(taskId);
+ final Rect stackBounds = stackInfo.bounds;
+ stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET);
+ final Rect currentTaskBounds = new Rect(initialTaskBounds);
+
+ // Size by top-left
+ pw.println("Growing top-left");
+ pw.flush();
+ do {
+ currentTaskBounds.top -= getStepSize(
+ currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
+
+ currentTaskBounds.left -= getStepSize(
+ currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (stackBounds.top < currentTaskBounds.top
+ || stackBounds.left < currentTaskBounds.left);
+
+ // Back to original size
+ pw.println("Shrinking top-left");
+ pw.flush();
+ do {
+ currentTaskBounds.top += getStepSize(
+ currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
+
+ currentTaskBounds.left += getStepSize(
+ currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (initialTaskBounds.top > currentTaskBounds.top
+ || initialTaskBounds.left > currentTaskBounds.left);
+
+ // Size by top-right
+ pw.println("Growing top-right");
+ pw.flush();
+ do {
+ currentTaskBounds.top -= getStepSize(
+ currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
+
+ currentTaskBounds.right += getStepSize(
+ currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (stackBounds.top < currentTaskBounds.top
+ || stackBounds.right > currentTaskBounds.right);
+
+ // Back to original size
+ pw.println("Shrinking top-right");
+ pw.flush();
+ do {
+ currentTaskBounds.top += getStepSize(
+ currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
+
+ currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
+ stepSize, GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (initialTaskBounds.top > currentTaskBounds.top
+ || initialTaskBounds.right < currentTaskBounds.right);
+
+ // Size by bottom-left
+ pw.println("Growing bottom-left");
+ pw.flush();
+ do {
+ currentTaskBounds.bottom += getStepSize(
+ currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
+
+ currentTaskBounds.left -= getStepSize(
+ currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (stackBounds.bottom > currentTaskBounds.bottom
+ || stackBounds.left < currentTaskBounds.left);
+
+ // Back to original size
+ pw.println("Shrinking bottom-left");
+ pw.flush();
+ do {
+ currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
+ initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
+
+ currentTaskBounds.left += getStepSize(
+ currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (initialTaskBounds.bottom < currentTaskBounds.bottom
+ || initialTaskBounds.left > currentTaskBounds.left);
+
+ // Size by bottom-right
+ pw.println("Growing bottom-right");
+ pw.flush();
+ do {
+ currentTaskBounds.bottom += getStepSize(
+ currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
+
+ currentTaskBounds.right += getStepSize(
+ currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (stackBounds.bottom > currentTaskBounds.bottom
+ || stackBounds.right > currentTaskBounds.right);
+
+ // Back to original size
+ pw.println("Shrinking bottom-right");
+ pw.flush();
+ do {
+ currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
+ initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
+
+ currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
+ stepSize, GREATER_THAN_TARGET);
+
+ taskResize(taskId, currentTaskBounds, delay_ms, true);
+ } while (initialTaskBounds.bottom < currentTaskBounds.bottom
+ || initialTaskBounds.right < currentTaskBounds.right);
+ return 0;
+ }
+
+ int runWrite(PrintWriter pw) {
+ mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "registerUidObserver()");
+ mInternal.mRecentTasks.flush();
+ pw.println("All tasks persisted.");
+ 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();
@@ -202,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");
@@ -222,25 +2519,195 @@
} else {
pw.println("Activity manager (activity) commands:");
pw.println(" help");
- pw.println(" Print this help text.");
+ pw.println(" Print this help text.");
+ pw.println(" start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]");
+ pw.println(" [--sampling INTERVAL] [-R COUNT] [-S]");
+ pw.println(" [--track-allocation] [--user <USER_ID> | current] <INTENT>");
+ pw.println(" Start an Activity. Options are:");
+ pw.println(" -D: enable debugging");
+ pw.println(" -N: enable native debugging");
+ pw.println(" -W: wait for launch to complete");
+ pw.println(" --start-profiler <FILE>: start profiler and send results to <FILE>");
+ pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
+ pw.println(" between samples (use with --start-profiler)");
+ pw.println(" -P <FILE>: like above, but profiling stops when app goes idle");
+ pw.println(" -R: repeat the activity launch <COUNT> times. Prior to each repeat,");
+ pw.println(" the top activity will be finished.");
+ pw.println(" -S: force stop the target app before starting the activity");
+ pw.println(" --track-allocation: enable tracking of object allocations");
+ pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
+ pw.println(" specified then run as the current user.");
+ pw.println(" --stack <STACK_ID>: Specify into which stack should the activity be put.");
+ pw.println(" start-service [--user <USER_ID> | current] <INTENT>");
+ pw.println(" Start a Service. Options are:");
+ pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
+ pw.println(" specified then run as the current user.");
+ pw.println(" stop-service [--user <USER_ID> | current] <INTENT>");
+ pw.println(" Stop a Service. Options are:");
+ pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
+ pw.println(" specified then run as the current user.");
+ pw.println(" broadcast [--user <USER_ID> | all | current] <INTENT>");
+ pw.println(" Send a broadcast Intent. Options are:");
+ pw.println(" --user <USER_ID> | all | current: Specify which user to send to; if not");
+ pw.println(" specified then send to all users.");
+ pw.println(" --receiver-permission <PERMISSION>: Require receiver to hold permission.");
+ pw.println(" trace-ipc [start|stop] [--dump-file <FILE>]");
+ pw.println(" Trace IPC transactions.");
+ pw.println(" start: start tracing IPC transactions.");
+ pw.println(" stop: stop tracing IPC transactions and dump the results to file.");
+ pw.println(" --dump-file <FILE>: Specify the file the trace should be dumped to.");
+ pw.println(" profile [start|stop] [--user <USER_ID> current] [--sampling INTERVAL]");
+ pw.println(" <PROCESS> <FILE>");
+ pw.println(" Start and stop profiler on a process. The given <PROCESS> argument");
+ pw.println(" may be either a process name or pid. Options are:");
+ pw.println(" --user <USER_ID> | current: When supplying a process name,");
+ pw.println(" specify user of process to profile; uses current user if not specified.");
+ pw.println(" dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>");
+ pw.println(" Dump the heap of a process. The given <PROCESS> argument may");
+ pw.println(" be either a process name or pid. Options are:");
+ pw.println(" -n: dump native heap instead of managed heap");
+ pw.println(" --user <USER_ID> | current: When supplying a process name,");
+ pw.println(" specify user of process to dump; uses current user if not specified.");
+ pw.println(" set-debug-app [-w] [--persistent] <PACKAGE>");
+ pw.println(" Set application <PACKAGE> to debug. Options are:");
+ pw.println(" -w: wait for debugger when application starts");
+ pw.println(" --persistent: retain this value");
+ pw.println(" clear-debug-app");
+ pw.println(" Clear the previously set-debug-app.");
+ pw.println(" set-watch-heap <PROCESS> <MEM-LIMIT>");
+ pw.println(" Start monitoring pss size of <PROCESS>, if it is at or");
+ pw.println(" above <HEAP-LIMIT> then a heap dump is collected for the user to report.");
+ pw.println(" clear-watch-heap");
+ pw.println(" Clear the previously set-watch-heap.");
+ pw.println(" bug-report [--progress]");
+ pw.println(" Request bug report generation; will launch a notification");
+ pw.println(" when done to select where it should be delivered. Options are:");
+ pw.println(" --progress: will launch a notification right away to show its progress.");
pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>");
- pw.println(" Completely stop the given application package.");
+ pw.println(" Completely stop the given application package.");
pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>");
- pw.println(" Kill all processes associated with the given application.");
+ pw.println(" Kill all processes associated with the given application.");
pw.println(" kill-all");
- pw.println(" Kill all processes that are safe to kill (cached, etc).");
- pw.println(" write");
- pw.println(" Write all pending state to storage.");
- pw.println(" track-associations");
- pw.println(" Enable association tracking.");
- pw.println(" untrack-associations");
- pw.println(" Disable and clear association tracking.");
+ pw.println(" Kill all processes that are safe to kill (cached, etc).");
+ pw.println(" monitor [--gdb <port>]");
+ pw.println(" Start monitoring for crashes or ANRs.");
+ pw.println(" --gdb: start gdbserv on the given port at crash/ANR");
+ pw.println(" hang [--allow-restart]");
+ pw.println(" Hang the system.");
+ pw.println(" --allow-restart: allow watchdog to perform normal system restart");
+ pw.println(" restart");
+ pw.println(" Restart the user-space system.");
+ pw.println(" idle-maintenance");
+ pw.println(" Perform idle maintenance now.");
+ pw.println(" screen-compat [on|off] <PACKAGE>");
+ pw.println(" Control screen compatibility mode of <PACKAGE>.");
+ pw.println(" package-importance <PACKAGE>");
+ pw.println(" Print current importance of <PACKAGE>.");
+ pw.println(" to-uri [INTENT]");
+ pw.println(" Print the given Intent specification as a URI.");
+ pw.println(" to-intent-uri [INTENT]");
+ pw.println(" Print the given Intent specification as an intent: URI.");
+ pw.println(" to-app-uri [INTENT]");
+ pw.println(" Print the given Intent specification as an android-app: URI.");
+ pw.println(" switch-user <USER_ID>");
+ pw.println(" Switch to put USER_ID in the foreground, starting");
+ pw.println(" execution of that user if it is currently stopped.");
+ pw.println(" get-current-user");
+ pw.println(" Returns id of the current foreground user.");
+ pw.println(" start-user <USER_ID>");
+ pw.println(" Start USER_ID in background if it is currently stopped;");
+ pw.println(" use switch-user if you want to start the user in foreground");
+ pw.println(" unlock-user <USER_ID> [TOKEN_HEX]");
+ pw.println(" Attempt to unlock the given user using the given authorization token.");
+ pw.println(" stop-user [-w] [-f] <USER_ID>");
+ pw.println(" Stop execution of USER_ID, not allowing it to run any");
+ pw.println(" code until a later explicit start or switch to it.");
+ pw.println(" -w: wait for stop-user to complete.");
+ pw.println(" -f: force stop even if there are related users that cannot be stopped.");
pw.println(" is-user-stopped <USER_ID>");
- pw.println(" Returns whether <USER_ID> has been stopped or not.");
+ pw.println(" Returns whether <USER_ID> has been stopped or not.");
+ pw.println(" get-started-user-state <USER_ID>");
+ pw.println(" Gets the current state of the given started user.");
+ pw.println(" track-associations");
+ pw.println(" Enable association tracking.");
+ pw.println(" untrack-associations");
+ pw.println(" Disable and clear association tracking.");
pw.println(" lenient-background-check [<true|false>]");
- pw.println(" Optionally controls lenient background check mode, returns current mode.");
+ 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(" 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>");
+ pw.println(" Suppresses configuration changes due to user resizing an activity/task.");
+ pw.println(" set-inactive [--user <USER_ID>] <PACKAGE> true|false");
+ pw.println(" Sets the inactive state of an app.");
+ pw.println(" get-inactive [--user <USER_ID>] <PACKAGE>");
+ pw.println(" Returns the inactive state of an app.");
+ pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>");
+ pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
+ pw.println(" Send a memory trim event to a <PROCESS>.");
+ pw.println(" stack [COMMAND] [...]: sub-commands for operating on activity stacks.");
+ pw.println(" start <DISPLAY_ID> <INTENT>");
+ pw.println(" Start a new activity on <DISPLAY_ID> using <INTENT>");
+ pw.println(" movetask <TASK_ID> <STACK_ID> [true|false]");
+ pw.println(" Move <TASK_ID> from its current stack to the top (true) or");
+ pw.println(" bottom (false) of <STACK_ID>.");
+ pw.println(" resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
+ pw.println(" Change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>.");
+ pw.println(" resize-animated <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
+ pw.println(" Same as resize, but allow animation.");
+ pw.println(" resize-docked-stack <LEFT,TOP,RIGHT,BOTTOM> [<TASK_LEFT,TASK_TOP,TASK_RIGHT,TASK_BOTTOM>]");
+ pw.println(" Change docked stack to <LEFT,TOP,RIGHT,BOTTOM>");
+ pw.println(" and supplying temporary different task bounds indicated by");
+ pw.println(" <TASK_LEFT,TOP,RIGHT,BOTTOM>");
+ pw.println(" size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]");
+ pw.println(" Test command for sizing docked stack by");
+ pw.println(" <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom");
+ pw.println(" applying the optional [DELAY_MS] between each step.");
+ pw.println(" move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
+ pw.println(" Moves the top activity from");
+ pw.println(" <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the");
+ pw.println(" bounds of the pinned stack.");
+ pw.println(" positiontask <TASK_ID> <STACK_ID> <POSITION>");
+ pw.println(" Place <TASK_ID> in <STACK_ID> at <POSITION>");
+ pw.println(" list");
+ pw.println(" List all of the activity stacks and their sizes.");
+ pw.println(" info <STACK_ID>");
+ pw.println(" Display the information about activity stack <STACK_ID>.");
+ pw.println(" remove <STACK_ID>");
+ pw.println(" Remove stack <STACK_ID>.");
+ pw.println(" task [COMMAND] [...]: sub-commands for operating on activity tasks.");
+ pw.println(" lock <TASK_ID>");
+ pw.println(" Bring <TASK_ID> to the front and don't allow other tasks to run.");
+ pw.println(" lock stop");
+ pw.println(" End the current task lock.");
+ pw.println(" resizeable <TASK_ID> [0|1|2|3]");
+ pw.println(" Change resizeable mode of <TASK_ID> to one of the following:");
+ pw.println(" 0: unresizeable");
+ pw.println(" 1: crop_windows");
+ pw.println(" 2: resizeable");
+ pw.println(" 3: resizeable_and_pipable");
+ pw.println(" resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
+ pw.println(" Makes sure <TASK_ID> is in a stack with the specified bounds.");
+ pw.println(" Forces the task to be resizeable and creates a stack if no existing stack");
+ pw.println(" has the specified bounds.");
+ pw.println(" drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
+ pw.println(" Test command for dragging/moving <TASK_ID> by");
+ pw.println(" <STEP_SIZE> increments around the screen applying the optional [DELAY_MS]");
+ pw.println(" between each step.");
+ pw.println(" size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
+ 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();
+ Intent.printIntentArgsHelp(pw, "");
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e52671f..abe4f30 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -20,22 +20,38 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SCREENSHOTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_THUMBNAILS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SAVED_STATE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SCREENSHOTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_THUMBNAILS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.TAKE_FULLSCREEN_SCREENSHOTS;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import android.annotation.NonNull;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.PendingIntent;
@@ -50,6 +66,7 @@
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
import android.os.PersistableBundle;
@@ -81,6 +98,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import org.xmlpull.v1.XmlPullParser;
@@ -92,12 +110,16 @@
*/
final class ActivityRecord {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM;
+ private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
+ private static final String TAG_SAVED_STATE = TAG + POSTFIX_SAVED_STATE;
+ private static final String TAG_SCREENSHOTS = TAG + POSTFIX_SCREENSHOTS;
private static final String TAG_STATES = TAG + POSTFIX_STATES;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
private static final String TAG_THUMBNAILS = TAG + POSTFIX_THUMBNAILS;
+ private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
private static final boolean SHOW_ACTIVITY_START_TIME = true;
- final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
+ private static final String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
private static final String ATTR_ID = "id";
private static final String TAG_INTENT = "intent";
@@ -150,11 +172,11 @@
long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity
long pauseTime; // last time we started pausing the activity
long launchTickTime; // base time for launch tick messages
- Configuration configuration; // configuration activity was last running in
+ private Configuration mLastReportedConfiguration; // configuration activity was last running in
// Overridden configuration by the activity task
- // WARNING: Reference points to {@link TaskRecord#mOverrideConfig}, so its internal state
- // should never be altered directly.
- Configuration taskConfigOverride;
+ // WARNING: Reference points to {@link TaskRecord#getMergedOverrideConfig}, so its internal
+ // state should never be altered directly.
+ private Configuration mLastReportedOverrideConfiguration;
CompatibilityInfo compat;// last used compatibility mode
ActivityRecord resultTo; // who started this entry, so will get our reply
final String resultWho; // additional identifier for use by resultTo.
@@ -231,6 +253,12 @@
// on the window.
int mRotationAnimationHint = -1;
+ /**
+ * Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
+ */
+ private final Configuration mTmpGlobalConfig = new Configuration();
+ private final Configuration mTmpTaskConfig = new Configuration();
+
private static String startingWindowStateToString(int state) {
switch (state) {
case STARTING_WINDOW_NOT_SHOWN:
@@ -279,8 +307,10 @@
pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes));
pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
pw.print(" theme=0x"); pw.println(Integer.toHexString(theme));
- pw.print(prefix); pw.print("config="); pw.println(configuration);
- pw.print(prefix); pw.print("taskConfigOverride="); pw.println(taskConfigOverride);
+ pw.print(prefix); pw.print("mLastReportedConfiguration=");
+ pw.println(mLastReportedConfiguration);
+ pw.print(prefix); pw.print("mLastReportedOverrideConfiguration=");
+ pw.println(mLastReportedOverrideConfiguration);
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultWho="); pw.print(resultWho);
@@ -401,15 +431,15 @@
}
}
- public boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
+ private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
return crossesSizeThreshold(mHorizontalSizeConfigurations, firstDp, secondDp);
}
- public boolean crossesVerticalSizeThreshold(int firstDp, int secondDp) {
+ private boolean crossesVerticalSizeThreshold(int firstDp, int secondDp) {
return crossesSizeThreshold(mVerticalSizeConfigurations, firstDp, secondDp);
}
- public boolean crossesSmallestSizeThreshold(int firstDp, int secondDp) {
+ private boolean crossesSmallestSizeThreshold(int firstDp, int secondDp) {
return crossesSizeThreshold(mSmallestSizeConfigurations, firstDp, secondDp);
}
@@ -442,14 +472,14 @@
return false;
}
- public void setSizeConfigurations(int[] horizontalSizeConfiguration,
+ void setSizeConfigurations(int[] horizontalSizeConfiguration,
int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
mHorizontalSizeConfigurations = horizontalSizeConfiguration;
mVerticalSizeConfigurations = verticalSizeConfigurations;
mSmallestSizeConfigurations = smallestSizeConfigurations;
}
- void scheduleConfigurationChanged(Configuration config, boolean reportToActivity) {
+ private void scheduleConfigurationChanged(Configuration config, boolean reportToActivity) {
if (app == null || app.thread == null) {
return;
}
@@ -457,14 +487,15 @@
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + " " +
"reportToActivity=" + reportToActivity + " and config: " + config);
- app.thread.scheduleActivityConfigurationChanged(appToken, config, reportToActivity);
+ app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config),
+ reportToActivity);
} catch (RemoteException e) {
// If process died, whatever.
}
}
void scheduleMultiWindowModeChanged() {
- if (task == null || task.stack == null || app == null || app.thread == null) {
+ if (task == null || task.getStack() == null || app == null || app.thread == null) {
return;
}
try {
@@ -476,20 +507,19 @@
}
void schedulePictureInPictureModeChanged() {
- if (task == null || task.stack == null || app == null || app.thread == null) {
+ if (task == null || task.getStack() == null || app == null || app.thread == null) {
return;
}
try {
app.thread.schedulePictureInPictureModeChanged(
- appToken, task.stack.mStackId == PINNED_STACK_ID);
+ appToken, task.getStackId() == PINNED_STACK_ID);
} catch (Exception e) {
// If process died, no one cares.
}
}
boolean isFreeform() {
- return task != null && task.stack != null
- && task.stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
+ return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
}
static class Token extends IApplicationToken.Stub {
@@ -528,7 +558,6 @@
if (r != null) {
if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsGone(): " + r);
r.nowVisible = false;
- return;
}
}
}
@@ -544,7 +573,7 @@
return false;
}
anrActivity = r.getWaitingHistoryRecordLocked();
- anrApp = r != null ? r.app : null;
+ anrApp = r.app;
}
return mService.inputDispatchingTimedOut(anrApp, anrActivity, r, false, reason);
}
@@ -561,12 +590,12 @@
}
}
- private static final ActivityRecord tokenToActivityRecordLocked(Token token) {
+ private static ActivityRecord tokenToActivityRecordLocked(Token token) {
if (token == null) {
return null;
}
ActivityRecord r = token.weakActivity.get();
- if (r == null || r.task == null || r.task.stack == null) {
+ if (r == null || r.getStack() == null) {
return null;
}
return r;
@@ -615,8 +644,8 @@
resolvedType = _resolvedType;
componentSpecified = _componentSpecified;
rootVoiceInteraction = _rootVoiceInteraction;
- configuration = _configuration;
- taskConfigOverride = Configuration.EMPTY;
+ mLastReportedConfiguration = new Configuration(_configuration);
+ mLastReportedOverrideConfiguration = new Configuration();
resultTo = _resultTo;
resultWho = _resultWho;
requestCode = _reqCode;
@@ -769,6 +798,12 @@
&& isHomeIntent(intent) && !isResolverActivity()) {
// This sure looks like a home activity!
mActivityType = HOME_ACTIVITY_TYPE;
+
+ if (info.resizeMode == RESIZE_MODE_FORCE_RESIZEABLE
+ || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // We only allow home activities to be resizeable if they explicitly requested it.
+ info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ }
} else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
mActivityType = RECENTS_ACTIVITY_TYPE;
} else {
@@ -777,8 +812,9 @@
}
void setTask(TaskRecord newTask, TaskRecord taskToAffiliateWith) {
- if (task != null && task.removeActivity(this) && task != newTask && task.stack != null) {
- task.stack.removeTask(task, "setTask");
+ if (task != null && task.removeActivity(this) && task != newTask
+ && task.getStack() != null) {
+ task.getStack().removeTask(task, "setTask");
}
task = newTask;
setTaskToAffiliateWith(taskToAffiliateWith);
@@ -792,6 +828,13 @@
}
}
+ /**
+ * @return Stack value from current task, null if there is no task.
+ */
+ ActivityStack getStack() {
+ return task != null ? task.getStack() : null;
+ }
+
boolean changeWindowTranslucency(boolean toOpaque) {
if (fullscreen == toOpaque) {
return false;
@@ -825,7 +868,8 @@
}
boolean isInStackLocked() {
- return task != null && task.stack != null && task.stack.isInStackLocked(this) != null;
+ final ActivityStack stack = getStack();
+ return stack != null && stack.isInStackLocked(this) != null;
}
boolean isHomeActivity() {
@@ -848,11 +892,11 @@
}
boolean isFocusable() {
- return StackId.canReceiveKeys(task.stack.mStackId) || isAlwaysFocusable();
+ return StackId.canReceiveKeys(task.getStackId()) || isAlwaysFocusable();
}
boolean isResizeable() {
- return !isHomeActivity() && ActivityInfo.isResizeableMode(info.resizeMode);
+ return ActivityInfo.isResizeableMode(info.resizeMode);
}
boolean isResizeableOrForced() {
@@ -860,8 +904,9 @@
}
boolean isNonResizableOrForced() {
- return !isHomeActivity() && info.resizeMode != RESIZE_MODE_RESIZEABLE
- && info.resizeMode != RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+ return info.resizeMode != RESIZE_MODE_RESIZEABLE
+ && info.resizeMode != RESIZE_MODE_RESIZEABLE_AND_PIPABLE
+ && info.resizeMode != RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
}
boolean supportsPictureInPicture() {
@@ -869,22 +914,17 @@
}
boolean canGoInDockedStack() {
- return !isHomeActivity()
- && (isResizeableOrForced() || info.resizeMode == RESIZE_MODE_CROP_WINDOWS);
+ return !isHomeActivity() && isResizeableOrForced();
}
boolean isAlwaysFocusable() {
return (info.flags & FLAG_ALWAYS_FOCUSABLE) != 0;
}
- boolean isOnTopLauncher() {
- return isHomeActivity() && (info.flags & FLAG_ON_TOP_LAUNCHER) != 0;
- }
-
void makeFinishingLocked() {
if (!finishing) {
- if (task != null && task.stack != null
- && this == task.stack.getVisibleBehindActivity()) {
+ final ActivityStack stack = getStack();
+ if (stack != null && this == stack.getVisibleBehindActivity()) {
// A finishing activity should not remain as visible in the background
mStackSupervisor.requestVisibleBehindLocked(this, false);
}
@@ -931,7 +971,7 @@
}
}
- void addNewIntentLocked(ReferrerIntent intent) {
+ private void addNewIntentLocked(ReferrerIntent intent) {
if (newIntents == null) {
newIntents = new ArrayList<>();
}
@@ -948,7 +988,7 @@
intent, getUriPermissionsLocked(), userId);
final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
boolean unsent = true;
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = getStack();
final boolean isTopActivityInStack =
stack != null && stack.topRunningActivityLocked() == this;
final boolean isTopActivityWhileSleeping =
@@ -1112,12 +1152,251 @@
"Setting thumbnail of " + this + " to " + newThumbnail);
boolean thumbnailUpdated = task.setLastThumbnailLocked(newThumbnail);
if (thumbnailUpdated && isPersistable()) {
- mStackSupervisor.mService.notifyTaskPersisterLocked(task, false);
+ service.notifyTaskPersisterLocked(task, false);
}
}
task.lastDescription = description;
}
+ final Bitmap screenshotActivityLocked() {
+ if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivityLocked: " + this);
+ if (noDisplay) {
+ if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tNo display");
+ return null;
+ }
+
+ final ActivityStack stack = getStack();
+ if (stack.isHomeStack()) {
+ // This is an optimization -- since we never show Home or Recents within Recents itself,
+ // we can just go ahead and skip taking the screenshot if this is the home stack.
+ if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tHome stack");
+ return null;
+ }
+
+ int w = service.mThumbnailWidth;
+ int h = service.mThumbnailHeight;
+
+ if (w <= 0) {
+ Slog.e(TAG, "\tInvalid thumbnail dimensions: " + w + "x" + h);
+ return null;
+ }
+
+ if (stack.mStackId == DOCKED_STACK_ID && mStackSupervisor.mIsDockMinimized) {
+ // When the docked stack is minimized its app windows are cropped significantly so any
+ // screenshot taken will not display the apps contain. So, we avoid taking a screenshot
+ // in that case.
+ if (DEBUG_SCREENSHOTS) Slog.e(TAG, "\tIn minimized docked stack");
+ return null;
+ }
+
+ final float scale;
+ if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
+
+ // When this flag is set, we currently take the fullscreen screenshot of the activity but
+ // scaled to half the size. This gives us a "good-enough" fullscreen thumbnail to use within
+ // SystemUI while keeping memory usage low.
+ if (TAKE_FULLSCREEN_SCREENSHOTS) {
+ w = h = -1;
+ scale = service.mFullscreenThumbnailScale;
+ }
+
+ return service.mWindowManager.screenshotApplications(appToken, DEFAULT_DISPLAY, w, h,
+ scale);
+ }
+
+ void setVisible(boolean newVisible) {
+ visible = newVisible;
+ if (!visible && mUpdateTaskThumbnailWhenHidden) {
+ updateThumbnailLocked(screenshotActivityLocked(), null /* description */);
+ mUpdateTaskThumbnailWhenHidden = false;
+ }
+ service.mWindowManager.setAppVisibility(appToken, visible);
+ final ArrayList<ActivityContainer> containers = mChildContainers;
+ for (int containerNdx = containers.size() - 1; containerNdx >= 0; --containerNdx) {
+ final ActivityContainer container = containers.get(containerNdx);
+ container.setVisible(visible);
+ }
+ mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
+ }
+
+ /** Return true if the input activity should be made visible */
+ boolean shouldBeVisible(boolean behindTranslucentActivity, boolean stackVisibleBehind,
+ ActivityRecord visibleBehind, boolean behindFullscreenActivity) {
+ if (!okToShowLocked()) {
+ return false;
+ }
+
+ // mLaunchingBehind: Activities launching behind are at the back of the task stack
+ // but must be drawn initially for the animation as though they were visible.
+ final boolean activityVisibleBehind =
+ (behindTranslucentActivity || stackVisibleBehind) && visibleBehind == this;
+
+ boolean isVisible =
+ !behindFullscreenActivity || mLaunchTaskBehind || activityVisibleBehind;
+
+ if (service.mSupportsLeanbackOnly && isVisible && isRecentsActivity()) {
+ // On devices that support leanback only (Android TV), Recents activity can only be
+ // visible if the home stack is the focused stack or we are in split-screen mode.
+ isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null
+ || mStackSupervisor.isFocusedStack(getStack());
+ }
+
+ return isVisible;
+ }
+
+ void makeVisibleIfNeeded(ActivityRecord starting) {
+ // This activity is not currently visible, but is running. Tell it to become visible.
+ if (state == ActivityState.RESUMED || this == starting) {
+ if (DEBUG_VISIBILITY) Slog.d(TAG_VISIBILITY,
+ "Not making visible, r=" + this + " state=" + state + " starting=" + starting);
+ return;
+ }
+
+ // If this activity is paused, tell it to now show its window.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Making visible and scheduling visibility: " + this);
+ final ActivityStack stack = getStack();
+ try {
+ if (stack.mTranslucentActivityWaiting != null) {
+ updateOptionsLocked(returningOptions);
+ stack.mUndrawnActivitiesBelowTopTranslucent.add(this);
+ }
+ setVisible(true);
+ sleeping = false;
+ app.pendingUiClean = true;
+ app.thread.scheduleWindowVisibility(appToken, true /* showWindow */);
+ // The activity may be waiting for stop, but that is no longer appropriate for it.
+ mStackSupervisor.mStoppingActivities.remove(this);
+ mStackSupervisor.mGoingToSleepActivities.remove(this);
+ } catch (Exception e) {
+ // Just skip on any failure; we'll make it visible when it next restarts.
+ Slog.w(TAG, "Exception thrown making visibile: " + intent.getComponent(), e);
+ }
+ handleAlreadyVisible();
+ }
+
+ boolean handleAlreadyVisible() {
+ stopFreezingScreenLocked(false);
+ try {
+ if (returningOptions != null) {
+ app.thread.scheduleOnNewActivityOptions(appToken, returningOptions.toBundle());
+ }
+ } catch(RemoteException e) {
+ }
+ return state == ActivityState.RESUMED;
+ }
+
+ static void activityResumedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
+ if (r != null) {
+ r.icicle = null;
+ r.haveState = false;
+ }
+ }
+
+ /**
+ * Once we know that we have asked an application to put an activity in the resumed state
+ * (either by launching it or explicitly telling it), this function updates the rest of our
+ * state to match that fact.
+ */
+ void completeResumeLocked() {
+ final boolean wasVisible = visible;
+ visible = true;
+ if (!wasVisible) {
+ // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
+ mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
+ }
+ idle = false;
+ results = null;
+ newIntents = null;
+ stopped = false;
+
+ if (isHomeActivity()) {
+ ProcessRecord app = task.mActivities.get(0).app;
+ if (app != null && app != service.mHomeProcess) {
+ service.mHomeProcess = app;
+ }
+ }
+
+ if (nowVisible) {
+ // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now.
+ mStackSupervisor.reportActivityVisibleLocked(this);
+ mStackSupervisor.notifyActivityDrawnForKeyguard();
+ }
+
+ // Schedule an idle timeout in case the app doesn't do it for us.
+ mStackSupervisor.scheduleIdleTimeoutLocked(this);
+
+ mStackSupervisor.reportResumedActivityLocked(this);
+
+ resumeKeyDispatchingLocked();
+ final ActivityStack stack = getStack();
+ stack.mNoAnimActivities.clear();
+
+ // Mark the point when the activity is resuming
+ // TODO: To be more accurate, the mark should be before the onCreate,
+ // not after the onResume. But for subsequent starts, onResume is fine.
+ if (app != null) {
+ cpuTimeAtResume = service.mProcessCpuTracker.getCpuTimeForPid(app.pid);
+ } else {
+ cpuTimeAtResume = 0; // Couldn't get the cpu time of process
+ }
+
+ returningOptions = null;
+
+ if (stack.getVisibleBehindActivity() == this) {
+ // When resuming an activity, require it to call requestVisibleBehind() again.
+ stack.setVisibleBehindActivity(null /* ActivityRecord */);
+ }
+ mStackSupervisor.checkReadyForSleepLocked();
+ }
+
+ final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState,
+ CharSequence description) {
+ final ActivityStack stack = getStack();
+ if (state != ActivityState.STOPPING) {
+ Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this);
+ stack.mHandler.removeMessages(ActivityStack.STOP_TIMEOUT_MSG, this);
+ return;
+ }
+ if (newPersistentState != null) {
+ persistentState = newPersistentState;
+ service.notifyTaskPersisterLocked(task, false);
+ }
+ if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + this + ": " + icicle);
+ if (newIcicle != null) {
+ // If icicle is null, this is happening due to a timeout, so we haven't really saved
+ // the state.
+ icicle = newIcicle;
+ haveState = true;
+ launchCount = 0;
+ updateThumbnailLocked(null /* newThumbnail */, description);
+ }
+ if (!stopped) {
+ if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)");
+ stack.mHandler.removeMessages(ActivityStack.STOP_TIMEOUT_MSG, this);
+ stopped = true;
+ state = ActivityState.STOPPED;
+
+ service.mWindowManager.notifyAppStopped(appToken);
+
+ if (stack.getVisibleBehindActivity() == this) {
+ mStackSupervisor.requestVisibleBehindLocked(this, false /* visible */);
+ }
+ if (finishing) {
+ clearOptionsLocked();
+ } else {
+ if (deferRelaunchUntilPaused) {
+ stack.destroyActivityLocked(this, true /* removeFromApp */, "stop-config");
+ mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ } else {
+ mStackSupervisor.updatePreviousProcessLocked(this);
+ }
+ }
+ }
+ }
+
void startLaunchTickingLocked() {
if (ActivityManagerService.IS_USER_BUILD) {
return;
@@ -1133,7 +1412,7 @@
return false;
}
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = getStack();
if (stack == null) {
return false;
}
@@ -1146,7 +1425,7 @@
void finishLaunchTickingLocked() {
launchTickTime = 0;
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = getStack();
if (stack != null) {
stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
}
@@ -1180,7 +1459,7 @@
if (displayStartTime != 0) {
reportLaunchTimeLocked(curTime);
}
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = getStack();
if (fullyDrawnStartTime != 0 && stack != null) {
final long thisTime = curTime - fullyDrawnStartTime;
final long totalTime = stack.mFullyDrawnStartTime != 0
@@ -1212,7 +1491,7 @@
}
private void reportLaunchTimeLocked(final long curTime) {
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = getStack();
if (stack == null) {
return;
}
@@ -1356,25 +1635,37 @@
static ActivityRecord isInStackLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return (r != null) ? r.task.stack.isInStackLocked(r) : null;
+ return (r != null) ? r.getStack().isInStackLocked(r) : null;
}
static ActivityStack getStackLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
- return r.task.stack;
+ return r.getStack();
}
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) {
// This would be redundant.
return false;
}
- if (task == null || task.stack == null || this == task.stack.mResumedActivity
- || this == task.stack.mPausingActivity || !haveState || !stopped) {
+ final ActivityStack stack = getStack();
+ if (stack == null || this == stack.mResumedActivity || this == stack.mPausingActivity
+ || !haveState || !stopped) {
// We're not ready for this kind of thing.
return false;
}
@@ -1424,6 +1715,275 @@
}
}
+ 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);
+ }
+
+ /** Call when override config was sent to the Window Manager to update internal records. */
+ void onOverrideConfigurationSent() {
+ mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
+ }
+
+ /**
+ * Make sure the given activity matches the current configuration. Returns false if the activity
+ * had to be destroyed. Returns true if the configuration is the same, or the activity will
+ * remain running as-is for whatever reason. Ensures the HistoryRecord is updated with the
+ * correct configuration and all other bookkeeping is handled.
+ */
+ boolean ensureActivityConfigurationLocked(int globalChanges, boolean preserveWindow) {
+ final ActivityStack stack = getStack();
+ if (stack.mConfigWillChange) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Skipping config check (will change): " + this);
+ return true;
+ }
+
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Ensuring correct configuration: " + this);
+
+ // Short circuit: if the two configurations are equal (the common case), then there is
+ // nothing to do.
+ final Configuration newGlobalConfig = service.getGlobalConfiguration();
+ final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
+ if (mLastReportedConfiguration.equals(newGlobalConfig)
+ && mLastReportedOverrideConfiguration.equals(newTaskMergedOverrideConfig)
+ && !forceNewConfig) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Configuration unchanged in " + this);
+ return true;
+ }
+
+ // We don't worry about activities that are finishing.
+ if (finishing) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Configuration doesn't matter in finishing " + this);
+ stopFreezingScreenLocked(false);
+ return true;
+ }
+
+ // Okay we now are going to make this activity have the new config.
+ // But then we need to figure out how it needs to deal with that.
+ mTmpGlobalConfig.setTo(mLastReportedConfiguration);
+ mTmpTaskConfig.setTo(mLastReportedOverrideConfiguration);
+ mLastReportedConfiguration.setTo(newGlobalConfig);
+ mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
+
+ int taskChanges = getTaskConfigurationChanges(this, newTaskMergedOverrideConfig,
+ mTmpTaskConfig);
+ final int changes = mTmpGlobalConfig.diff(newGlobalConfig) | taskChanges;
+ if (changes == 0 && !forceNewConfig) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Configuration no differences in " + this);
+ // There are no significant differences, so we won't relaunch but should still deliver
+ // the new configuration to the client process.
+ scheduleConfigurationChanged(newTaskMergedOverrideConfig, true);
+ return true;
+ }
+
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Configuration changes for " + this + " ; taskChanges="
+ + Configuration.configurationDiffToString(taskChanges) + ", allChanges="
+ + Configuration.configurationDiffToString(changes));
+
+ // If the activity isn't currently running, just leave the new configuration and it will
+ // pick that up next time it starts.
+ if (app == null || app.thread == null) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Configuration doesn't matter not running " + this);
+ stopFreezingScreenLocked(false);
+ forceNewConfig = false;
+ return true;
+ }
+
+ // Figure out how to handle the changes between the configurations.
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Checking to restart " + info.name + ": changed=0x"
+ + Integer.toHexString(changes) + ", handles=0x"
+ + Integer.toHexString(info.getRealConfigChanged())
+ + ", newGlobalConfig=" + newGlobalConfig
+ + ", newTaskMergedOverrideConfig=" + newTaskMergedOverrideConfig);
+
+ if ((changes&(~info.getRealConfigChanged())) != 0 || forceNewConfig) {
+ // Aha, the activity isn't handling the change, so DIE DIE DIE.
+ configChangeFlags |= changes;
+ startFreezingScreenLocked(app, globalChanges);
+ forceNewConfig = false;
+ preserveWindow &= isResizeOnlyChange(changes);
+ if (app == null || app.thread == null) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Config is destroying non-running " + this);
+ stack.destroyActivityLocked(this, true, "config");
+ } else if (state == ActivityState.PAUSING) {
+ // A little annoying: we are waiting for this activity to finish pausing. Let's not
+ // do anything now, but just flag that it needs to be restarted when done pausing.
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Config is skipping already pausing " + this);
+ deferRelaunchUntilPaused = true;
+ preserveWindowOnDeferredRelaunch = preserveWindow;
+ return true;
+ } else if (state == ActivityState.RESUMED) {
+ // Try to optimize this case: the configuration is changing and we need to restart
+ // the top, resumed activity. Instead of doing the normal handshaking, just say
+ // "restart!".
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Config is relaunching resumed " + this);
+
+ if (DEBUG_STATES && !visible) {
+ Slog.v(TAG_STATES, "Config is relaunching resumed invisible activity " + this
+ + " called by " + Debug.getCallers(4));
+ }
+
+ relaunchActivityLocked(true /* andResume */, preserveWindow);
+ } else {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Config is relaunching non-resumed " + this);
+ relaunchActivityLocked(false /* andResume */, preserveWindow);
+ }
+
+ // All done... tell the caller we weren't able to keep this activity around.
+ return false;
+ }
+
+ // Default case: the activity can handle this new configuration, so hand it over.
+ // NOTE: We only forward the task override configuration as the system level configuration
+ // changes is always sent to all processes when they happen so it can just use whatever
+ // system level configuration it last got.
+ scheduleConfigurationChanged(newTaskMergedOverrideConfig, true);
+ stopFreezingScreenLocked(false);
+
+ return true;
+ }
+
+ private static int getTaskConfigurationChanges(ActivityRecord record, Configuration taskConfig,
+ Configuration oldTaskOverride) {
+ // If we went from full-screen to non-full-screen, make sure to use the correct
+ // configuration task diff, so the diff stays as small as possible.
+ if (Configuration.EMPTY.equals(oldTaskOverride)
+ && !Configuration.EMPTY.equals(taskConfig)) {
+ oldTaskOverride = record.task.extractOverrideConfig(record.mLastReportedConfiguration);
+ }
+
+ // Conversely, do the same when going the other direction.
+ if (Configuration.EMPTY.equals(taskConfig)
+ && !Configuration.EMPTY.equals(oldTaskOverride)) {
+ taskConfig = record.task.extractOverrideConfig(record.mLastReportedConfiguration);
+ }
+
+ // Determine what has changed. May be nothing, if this is a config that has come back from
+ // the app after going idle. In that case we just want to leave the official config object
+ // now in the activity and do nothing else.
+ int taskChanges = oldTaskOverride.diff(taskConfig, true /* skipUndefined */);
+ // We don't want to use size changes if they don't cross boundaries that are important to
+ // the app.
+ if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
+ final boolean crosses = record.crossesHorizontalSizeThreshold(
+ oldTaskOverride.screenWidthDp, taskConfig.screenWidthDp)
+ || record.crossesVerticalSizeThreshold(
+ oldTaskOverride.screenHeightDp, taskConfig.screenHeightDp);
+ if (!crosses) {
+ taskChanges &= ~CONFIG_SCREEN_SIZE;
+ }
+ }
+ if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ final int oldSmallest = oldTaskOverride.smallestScreenWidthDp;
+ final int newSmallest = taskConfig.smallestScreenWidthDp;
+ if (!record.crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
+ taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ }
+ return taskChanges;
+ }
+
+ private static boolean isResizeOnlyChange(int change) {
+ return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
+ | CONFIG_SCREEN_LAYOUT)) == 0;
+ }
+
+ void relaunchActivityLocked(boolean andResume, boolean preserveWindow) {
+ if (service.mSuppressResizeConfigChanges && preserveWindow) {
+ configChangeFlags = 0;
+ return;
+ }
+
+ List<ResultInfo> pendingResults = null;
+ List<ReferrerIntent> pendingNewIntents = null;
+ if (andResume) {
+ pendingResults = results;
+ pendingNewIntents = newIntents;
+ }
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Relaunching: " + this + " with results=" + pendingResults
+ + " newIntents=" + pendingNewIntents + " andResume=" + andResume
+ + " preserveWindow=" + preserveWindow);
+ EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY
+ : EventLogTags.AM_RELAUNCH_ACTIVITY, userId, System.identityHashCode(this),
+ task.taskId, shortComponentName);
+
+ startFreezingScreenLocked(app, 0);
+
+ mStackSupervisor.removeChildActivityContainers(this);
+
+ try {
+ if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
+ "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + this
+ + " callers=" + Debug.getCallers(6));
+ forceNewConfig = false;
+ mStackSupervisor.activityRelaunchingLocked(this);
+ app.thread.scheduleRelaunchActivity(appToken, pendingResults, pendingNewIntents,
+ configChangeFlags, !andResume,
+ new Configuration(service.getGlobalConfiguration()),
+ new Configuration(task.getMergedOverrideConfiguration()), preserveWindow);
+ // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
+ // pass in 'andResume' if this activity is currently resumed, which implies we aren't
+ // sleeping.
+ } catch (RemoteException e) {
+ if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
+ }
+
+ if (andResume) {
+ if (DEBUG_STATES) {
+ Slog.d(TAG_STATES, "Resumed after relaunch " + this);
+ }
+ results = null;
+ newIntents = null;
+ service.showUnsupportedZoomDialogIfNeededLocked(this);
+ service.showAskCompatModeDialogLocked(this);
+ } else {
+ service.mHandler.removeMessages(ActivityStack.PAUSE_TIMEOUT_MSG, this);
+ state = ActivityState.PAUSED;
+ // if the app is relaunched when it's stopped, and we're not resuming,
+ // put it back into stopped state.
+ if (stopped) {
+ getStack().addToStopping(this, true /* immediate */);
+ }
+ }
+
+ configChangeFlags = 0;
+ deferRelaunchUntilPaused = false;
+ preserveWindowOnDeferredRelaunch = false;
+ }
+
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
out.attribute(null, ATTR_ID, String.valueOf(createTime));
out.attribute(null, ATTR_LAUNCHEDFROMUID, String.valueOf(launchedFromUid));
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7708b02..3dbbb13 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -22,26 +22,20 @@
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.res.Configuration.SCREENLAYOUT_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SCREENSHOTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
@@ -52,13 +46,11 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_APP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SAVED_STATE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SCREENSHOTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
@@ -69,7 +61,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.LOCK_SCREEN_SHOWN;
-import static com.android.server.am.ActivityManagerService.TAKE_FULLSCREEN_SCREENSHOTS;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_REMOVED;
@@ -99,7 +90,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
@@ -110,7 +100,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -124,7 +113,6 @@
import android.view.Display;
import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -143,19 +131,17 @@
/**
* State and management of a single stack of activities.
*/
-final class ActivityStack {
+final class ActivityStack extends ConfigurationContainer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
- private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
private static final String TAG_SAVED_STATE = TAG + POSTFIX_SAVED_STATE;
- private static final String TAG_SCREENSHOTS = TAG + POSTFIX_SCREENSHOTS;
private static final String TAG_STACK = TAG + POSTFIX_STACK;
private static final String TAG_STATES = TAG + POSTFIX_STATES;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -170,36 +156,47 @@
// How long we wait until giving up on the last activity to pause. This
// is short because it directly impacts the responsiveness of starting the
// next activity.
- static final int PAUSE_TIMEOUT = 500;
+ private static final int PAUSE_TIMEOUT = 500;
// How long we wait for the activity to tell us it has stopped before
// giving up. This is a good amount of time because we really need this
// from the application in order to get its saved state.
- static final int STOP_TIMEOUT = 10 * 1000;
+ private static final int STOP_TIMEOUT = 10 * 1000;
// How long we wait until giving up on an activity telling us it has
// finished destroying itself.
- static final int DESTROY_TIMEOUT = 10 * 1000;
+ private static final int DESTROY_TIMEOUT = 10 * 1000;
// How long until we reset a task when the user returns to it. Currently
// disabled.
- static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
-
- // How long between activity launches that we consider safe to not warn
- // the user about an unexpected activity being launched on top.
- static final long START_WARN_TIME = 5 * 1000;
+ private static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
// Set to false to disable the preview that is shown while a new activity
// is being started.
- static final boolean SHOW_APP_STARTING_PREVIEW = true;
+ private static final boolean SHOW_APP_STARTING_PREVIEW = true;
// How long to wait for all background Activities to redraw following a call to
// convertToTranslucent().
- static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
+ private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
// How many activities have to be scheduled to stop to force a stop pass.
private static final int MAX_STOPPING_TO_FORCE = 3;
+ @Override
+ protected int getChildCount() {
+ return mTaskHistory.size();
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return mTaskHistory.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mActivityContainer.mActivityDisplay;
+ }
+
enum ActivityState {
INITIALIZING,
RESUMED,
@@ -218,18 +215,18 @@
static final int STACK_VISIBLE = 1;
// Stack is considered visible, but only becuase it has activity that is visible behind other
// activities and there is a specific combination of stacks.
- static final int STACK_VISIBLE_ACTIVITY_BEHIND = 2;
+ private static final int STACK_VISIBLE_ACTIVITY_BEHIND = 2;
/* The various modes for the method {@link #removeTask}. */
// Task is being completely removed from all stacks in the system.
- static final int REMOVE_TASK_MODE_DESTROYING = 0;
+ private static final int REMOVE_TASK_MODE_DESTROYING = 0;
// Task is being removed from this stack so we can add it to another stack. In the case we are
// moving we don't want to perform some operations on the task like removing it from window
// manager or recents.
static final int REMOVE_TASK_MODE_MOVING = 1;
// Similar to {@link #REMOVE_TASK_MODE_MOVING} and the task will be added to the top of its new
// stack and the new stack will be on top of all stacks.
- static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2;
+ private static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2;
// The height/width divide used when fitting a task within a bounds with method
// {@link #fitWithinBounds}.
@@ -239,7 +236,7 @@
private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
final ActivityManagerService mService;
- final WindowManagerService mWindowManager;
+ private final WindowManagerService mWindowManager;
private final RecentTasks mRecentTasks;
/**
@@ -286,13 +283,6 @@
*/
ActivityRecord mResumedActivity = null;
- /**
- * This is the last activity that has been started. It is only used to
- * identify when multiple activities are started at once so that the user
- * can be warned they may not be in the activity they think they are.
- */
- ActivityRecord mLastStartedActivity = null;
-
// The topmost Activity passed to convertToTranslucent(). When non-null it means we are
// waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they
// are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the
@@ -300,7 +290,7 @@
// Activity.onTranslucentConversionComplete(false). If a timeout occurs prior to the last
// background activity being drawn then the same call will be made with a true value.
ActivityRecord mTranslucentActivityWaiting = null;
- private ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = new ArrayList<>();
+ ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = new ArrayList<>();
/**
* Set when we know we are going to be calling updateConfiguration()
@@ -313,11 +303,11 @@
// Current bounds of the stack or null if fullscreen.
Rect mBounds = null;
- boolean mUpdateBoundsDeferred;
- boolean mUpdateBoundsDeferredCalled;
- final Rect mDeferredBounds = new Rect();
- final Rect mDeferredTaskBounds = new Rect();
- final Rect mDeferredTaskInsetBounds = new Rect();
+ private boolean mUpdateBoundsDeferred;
+ private boolean mUpdateBoundsDeferredCalled;
+ private final Rect mDeferredBounds = new Rect();
+ private final Rect mDeferredTaskBounds = new Rect();
+ private final Rect mDeferredTaskInsetBounds = new Rect();
long mLaunchStartTime = 0;
long mFullyDrawnStartTime = 0;
@@ -338,7 +328,7 @@
private final Rect tempRect2 = new Rect();
/** Run all ActivityStacks through this */
- final ActivityStackSupervisor mStackSupervisor;
+ private final ActivityStackSupervisor mStackSupervisor;
private final LaunchingTaskPositioner mTaskPositioner;
@@ -351,7 +341,7 @@
static final int RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG =
ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 7;
- static class ScheduleDestroyArgs {
+ private static class ScheduleDestroyArgs {
final ProcessRecord mOwner;
final String mReason;
ScheduleDestroyArgs(ProcessRecord owner, String reason) {
@@ -362,7 +352,7 @@
final Handler mHandler;
- final class ActivityStackHandler extends Handler {
+ private class ActivityStackHandler extends Handler {
ActivityStackHandler(Looper looper) {
super(looper);
@@ -407,7 +397,8 @@
Slog.w(TAG, "Activity stop timeout for " + r);
synchronized (mService) {
if (r.isInHistory()) {
- activityStoppedLocked(r, null, null, null);
+ r.activityStoppedLocked(null /* icicle */,
+ null /* persistentState */, null /* description */);
}
}
} break;
@@ -466,6 +457,7 @@
mTaskPositioner.setDisplay(activityDisplay.mDisplay);
mTaskPositioner.configure(mBounds);
}
+ onParentChanged();
if (mStackId == DOCKED_STACK_ID) {
// If we created a docked stack we want to resize it so it resizes all other stacks
@@ -475,22 +467,24 @@
}
}
- void detachDisplay() {
+ void remove() {
mDisplayId = Display.INVALID_DISPLAY;
mStacks = null;
if (mTaskPositioner != null) {
mTaskPositioner.reset();
}
- mWindowManager.detachStack(mStackId);
if (mStackId == DOCKED_STACK_ID) {
// If we removed a docked stack we want to resize it so it resizes all other stacks
// in the system to fullscreen.
mStackSupervisor.resizeDockedStackLocked(
null, null, null, null, null, PRESERVE_WINDOWS);
}
+ mStackSupervisor.deleteActivityContainerRecord(mStackId);
+ mWindowManager.removeStack(mStackId);
+ onParentChanged();
}
- public void getDisplaySize(Point out) {
+ void getDisplaySize(Point out) {
mActivityContainer.mActivityDisplay.mDisplay.getSize(out);
}
@@ -644,9 +638,9 @@
return null;
}
final TaskRecord task = r.task;
- if (task != null && task.stack != null
- && task.mActivities.contains(r) && mTaskHistory.contains(task)) {
- if (task.stack != this) Slog.w(TAG,
+ final ActivityStack stack = r.getStack();
+ if (stack != null && task.mActivities.contains(r) && mTaskHistory.contains(task)) {
+ if (stack != this) Slog.w(TAG,
"Illegal state! task does not point to stack it is in.");
return r;
}
@@ -720,7 +714,7 @@
/**
* @param task If non-null, the task will be moved to the back of the stack.
* */
- void moveToBack(TaskRecord task) {
+ private void moveToBack(TaskRecord task) {
if (!isAttached()) {
return;
}
@@ -914,7 +908,7 @@
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + r + " (starting new instance)"
+ " callers=" + Debug.getCallers(5));
setResumedActivityLocked(r, "minimalResumeActivityLocked");
- completeResumeLocked(r);
+ r.completeResumeLocked();
setLaunchTime(r);
if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
"Launch completed; removing icicle of " + r.icicle);
@@ -955,7 +949,7 @@
}
}
- void clearLaunchTime(ActivityRecord r) {
+ private void clearLaunchTime(ActivityRecord r) {
// Make sure that there is no activity waiting for this to launch.
if (mStackSupervisor.mWaitingActivityLaunched.isEmpty()) {
r.displayStartTime = r.fullyDrawnStartTime = 0;
@@ -1037,50 +1031,6 @@
}
}
- public final Bitmap screenshotActivitiesLocked(ActivityRecord who) {
- if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivitiesLocked: " + who);
- if (who.noDisplay) {
- if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tNo display");
- return null;
- }
-
- if (isHomeStack()) {
- // This is an optimization -- since we never show Home or Recents within Recents itself,
- // we can just go ahead and skip taking the screenshot if this is the home stack.
- if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tHome stack");
- return null;
- }
-
- int w = mService.mThumbnailWidth;
- int h = mService.mThumbnailHeight;
-
- if (w <= 0) {
- Slog.e(TAG, "\tInvalid thumbnail dimensions: " + w + "x" + h);
- return null;
- }
-
- if (mStackId == DOCKED_STACK_ID && mStackSupervisor.mIsDockMinimized) {
- // When the docked stack is minimized its app windows are cropped significantly so any
- // screenshot taken will not display the apps contain. So, we avoid taking a screenshot
- // in that case.
- if (DEBUG_SCREENSHOTS) Slog.e(TAG, "\tIn minimized docked stack");
- return null;
- }
-
- float scale = 1f;
- if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
-
- // When this flag is set, we currently take the fullscreen screenshot of the activity but
- // scaled to half the size. This gives us a "good-enough" fullscreen thumbnail to use within
- // SystemUI while keeping memory usage low.
- if (TAKE_FULLSCREEN_SCREENSHOTS) {
- w = h = -1;
- scale = mService.mFullscreenThumbnailScale;
- }
-
- return mWindowManager.screenshotApplications(who.appToken, DEFAULT_DISPLAY, w, h, scale);
- }
-
/**
* Start pausing the currently resumed activity. It is an error to call this if there
* is already an activity being paused or there is no resumed activity.
@@ -1239,57 +1189,6 @@
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
- final void activityResumedLocked(IBinder token) {
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
- r.icicle = null;
- r.haveState = false;
- }
-
- final void activityStoppedLocked(ActivityRecord r, Bundle icicle,
- PersistableBundle persistentState, CharSequence description) {
- if (r.state != ActivityState.STOPPING) {
- Slog.i(TAG, "Activity reported stop, but no longer stopping: " + r);
- mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
- return;
- }
- if (persistentState != null) {
- r.persistentState = persistentState;
- mService.notifyTaskPersisterLocked(r.task, false);
- }
- if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + r + ": " + icicle);
- if (icicle != null) {
- // If icicle is null, this is happening due to a timeout, so we
- // haven't really saved the state.
- r.icicle = icicle;
- r.haveState = true;
- r.launchCount = 0;
- r.updateThumbnailLocked(null, description);
- }
- if (!r.stopped) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + r + " (stop complete)");
- mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
- r.stopped = true;
- r.state = ActivityState.STOPPED;
-
- mWindowManager.notifyAppStopped(r.appToken);
-
- if (getVisibleBehindActivity() == r) {
- mStackSupervisor.requestVisibleBehindLocked(r, false);
- }
- if (r.finishing) {
- r.clearOptionsLocked();
- } else {
- if (r.deferRelaunchUntilPaused) {
- destroyActivityLocked(r, true, "stop-config");
- mStackSupervisor.resumeFocusedStackTopActivityLocked();
- } else {
- mStackSupervisor.updatePreviousProcessLocked(r);
- }
- }
- }
- }
-
private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
ActivityRecord prev = mPausingActivity;
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
@@ -1310,7 +1209,7 @@
if (prev.deferRelaunchUntilPaused) {
// Complete the deferred relaunch that was waiting for pause to complete.
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Re-launching after pause: " + prev);
- relaunchActivityLocked(prev, prev.configChangeFlags, false,
+ prev.relaunchActivityLocked(false /* andResume */,
prev.preserveWindowOnDeferredRelaunch);
} else if (wasStopping) {
// We are also stopping, the stop request must have gone soon after the pause.
@@ -1387,7 +1286,7 @@
mStackSupervisor.ensureActivitiesVisibleLocked(resuming, 0, !PRESERVE_WINDOWS);
}
- private void addToStopping(ActivityRecord r, boolean immediate) {
+ void addToStopping(ActivityRecord r, boolean immediate) {
if (!mStackSupervisor.mStoppingActivities.contains(r)) {
mStackSupervisor.mStoppingActivities.add(r);
}
@@ -1408,72 +1307,6 @@
}
}
- /**
- * Once we know that we have asked an application to put an activity in
- * the resumed state (either by launching it or explicitly telling it),
- * this function updates the rest of our state to match that fact.
- */
- private void completeResumeLocked(ActivityRecord next) {
- next.visible = true;
- next.idle = false;
- next.results = null;
- next.newIntents = null;
- next.stopped = false;
-
- if (next.isHomeActivity()) {
- ProcessRecord app = next.task.mActivities.get(0).app;
- if (app != null && app != mService.mHomeProcess) {
- mService.mHomeProcess = app;
- }
- }
-
- if (next.nowVisible) {
- // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now.
- mStackSupervisor.reportActivityVisibleLocked(next);
- mStackSupervisor.notifyActivityDrawnForKeyguard();
- }
-
- // schedule an idle timeout in case the app doesn't do it for us.
- mStackSupervisor.scheduleIdleTimeoutLocked(next);
-
- mStackSupervisor.reportResumedActivityLocked(next);
-
- next.resumeKeyDispatchingLocked();
- mNoAnimActivities.clear();
-
- // Mark the point when the activity is resuming
- // TODO: To be more accurate, the mark should be before the onCreate,
- // not after the onResume. But for subsequent starts, onResume is fine.
- if (next.app != null) {
- next.cpuTimeAtResume = mService.mProcessCpuTracker.getCpuTimeForPid(next.app.pid);
- } else {
- next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process
- }
-
- next.returningOptions = null;
-
- if (getVisibleBehindActivity() == next) {
- // When resuming an activity, require it to call requestVisibleBehind() again.
- setVisibleBehindActivity(null);
- }
- mStackSupervisor.checkReadyForSleepLocked();
- }
-
- private void setVisible(ActivityRecord r, boolean visible) {
- r.visible = visible;
- if (!visible && r.mUpdateTaskThumbnailWhenHidden) {
- r.updateThumbnailLocked(r.task.stack.screenshotActivitiesLocked(r), null);
- r.mUpdateTaskThumbnailWhenHidden = false;
- }
- mWindowManager.setAppVisibility(r.appToken, visible);
- final ArrayList<ActivityContainer> containers = r.mChildContainers;
- for (int containerNdx = containers.size() - 1; containerNdx >= 0; --containerNdx) {
- ActivityContainer container = containers.get(containerNdx);
- container.setVisible(visible);
- }
- mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
- }
-
// Find the first visible activity above the passed activity and if it is translucent return it
// otherwise return null;
ActivityRecord findNextTranslucentActivity(ActivityRecord r) {
@@ -1482,7 +1315,7 @@
return null;
}
- ActivityStack stack = task.stack;
+ final ActivityStack stack = task.getStack();
if (stack == null) {
return null;
}
@@ -1526,7 +1359,7 @@
ArrayList<ActivityStack> stacks = mStacks;
final ActivityRecord parent = mActivityContainer.mParentActivity;
if (parent != null) {
- stacks = parent.task.stack.mStacks;
+ stacks = parent.getStack().mStacks;
}
if (stacks != null) {
for (int i = stacks.size() - 1; i >= 0; --i) {
@@ -1776,7 +1609,8 @@
// is finishing, we no longer change its visibility, but we still need to take
// the screenshots if startPausingLocked decided it should be taken.
if (r.mUpdateTaskThumbnailWhenHidden) {
- r.updateThumbnailLocked(screenshotActivitiesLocked(r), null);
+ r.updateThumbnailLocked(r.screenshotActivityLocked(),
+ null /* description */);
r.mUpdateTaskThumbnailWhenHidden = false;
}
continue;
@@ -1787,14 +1621,14 @@
}
aboveTop = false;
- if (shouldBeVisible(r, behindTranslucentActivity, stackVisibleBehind,
+ if (r.shouldBeVisible(behindTranslucentActivity, stackVisibleBehind,
visibleBehind, behindFullscreenActivity)) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
+ " finishing=" + r.finishing + " state=" + r.state);
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != starting) {
- ensureActivityConfigurationLocked(r, 0, preserveWindows);
+ r.ensureActivityConfigurationLocked(0 /* globalChanges */, preserveWindows);
}
if (r.app == null || r.app.thread == null) {
@@ -1812,11 +1646,11 @@
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Skipping: already visible at " + r);
- if (handleAlreadyVisible(r)) {
+ if (r.handleAlreadyVisible()) {
resumeNextActivity = false;
}
} else {
- makeVisibleIfNeeded(starting, r);
+ r.makeVisibleIfNeeded(starting);
}
// Aggregate current change flags.
configChanges |= r.configChangeFlags;
@@ -1897,33 +1731,6 @@
}
}
- /** Return true if the input activity should be made visible */
- private boolean shouldBeVisible(ActivityRecord r, boolean behindTranslucentActivity,
- boolean stackVisibleBehind, ActivityRecord visibleBehind,
- boolean behindFullscreenActivity) {
-
- if (r == null || !r.okToShowLocked()) {
- return false;
- }
-
- // mLaunchingBehind: Activities launching behind are at the back of the task stack
- // but must be drawn initially for the animation as though they were visible.
- final boolean activityVisibleBehind =
- (behindTranslucentActivity || stackVisibleBehind) && visibleBehind == r;
-
- boolean isVisible =
- !behindFullscreenActivity || r.mLaunchTaskBehind || activityVisibleBehind;
-
- if (mService.mSupportsLeanbackOnly && isVisible && r.isRecentsActivity()) {
- // On devices that support leanback only (Android TV), Recents activity can only be
- // visible if the home stack is the focused stack or we are in split-screen mode.
- isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null
- || mStackSupervisor.isFocusedStack(this);
- }
-
- return isVisible;
- }
-
private void checkTranslucentActivityWaiting(ActivityRecord top) {
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -1951,7 +1758,7 @@
}
if (!r.visible || r.mLaunchTaskBehind) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
- setVisible(r, true);
+ r.setVisible(true);
}
if (r != starting) {
mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
@@ -1970,7 +1777,7 @@
// keeping the screen frozen.
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.state);
try {
- setVisible(r, false);
+ r.setVisible(false);
switch (r.state) {
case STOPPING:
case STOPPED:
@@ -2020,50 +1827,6 @@
return behindFullscreenActivity;
}
- private void makeVisibleIfNeeded(ActivityRecord starting, ActivityRecord r) {
-
- // This activity is not currently visible, but is running. Tell it to become visible.
- if (r.state == ActivityState.RESUMED || r == starting) {
- if (DEBUG_VISIBILITY) Slog.d(TAG_VISIBILITY,
- "Not making visible, r=" + r + " state=" + r.state + " starting=" + starting);
- return;
- }
-
- // If this activity is paused, tell it to now show its window.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Making visible and scheduling visibility: " + r);
- try {
- if (mTranslucentActivityWaiting != null) {
- r.updateOptionsLocked(r.returningOptions);
- mUndrawnActivitiesBelowTopTranslucent.add(r);
- }
- setVisible(r, true);
- r.sleeping = false;
- r.app.pendingUiClean = true;
- r.app.thread.scheduleWindowVisibility(r.appToken, true);
- // The activity may be waiting for stop, but that is no longer
- // appropriate for it.
- mStackSupervisor.mStoppingActivities.remove(r);
- mStackSupervisor.mGoingToSleepActivities.remove(r);
- } catch (Exception e) {
- // Just skip on any failure; we'll make it
- // visible when it next restarts.
- Slog.w(TAG, "Exception thrown making visibile: " + r.intent.getComponent(), e);
- }
- handleAlreadyVisible(r);
- }
-
- private boolean handleAlreadyVisible(ActivityRecord r) {
- r.stopFreezingScreenLocked(false);
- try {
- if (r.returningOptions != null) {
- r.app.thread.scheduleOnNewActivityOptions(r.appToken, r.returningOptions);
- }
- } catch(RemoteException e) {
- }
- return r.state == ActivityState.RESUMED;
- }
-
void convertActivityToTranslucent(ActivityRecord r) {
mTranslucentActivityWaiting = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -2271,7 +2034,7 @@
}
final TaskRecord nextTask = next.task;
- if (prevTask != null && prevTask.stack == this &&
+ if (prevTask != null && prevTask.getStack() == this &&
prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
if (prevTask == nextTask) {
@@ -2523,13 +2286,14 @@
// the screen based on the new activity order.
boolean notUpdated = true;
if (mStackSupervisor.isFocusedStack(this)) {
- Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.mGlobalConfiguration,
- 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) {
@@ -2549,7 +2313,7 @@
if (!next.visible || next.stopped) {
mWindowManager.setAppVisibility(next.appToken, true);
}
- completeResumeLocked(next);
+ next.completeResumeLocked();
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
return true;
}
@@ -2623,7 +2387,7 @@
// From this point on, if something goes wrong there is no way
// to recover the activity.
try {
- completeResumeLocked(next);
+ next.completeResumeLocked();
} catch (Exception e) {
// If any exception gets thrown, toss away this
// activity and try the next one.
@@ -2898,7 +2662,7 @@
* @param forceReset
* @return An ActivityOptions that needs to be processed.
*/
- final ActivityOptions resetTargetTaskIfNeededLocked(TaskRecord task, boolean forceReset) {
+ private ActivityOptions resetTargetTaskIfNeededLocked(TaskRecord task, boolean forceReset) {
ActivityOptions topOptions = null;
int replyChainEnd = -1;
@@ -3480,7 +3244,7 @@
return true;
}
- final void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
+ private void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
// send the result
ActivityRecord resultTo = r.resultTo;
if (resultTo != null) {
@@ -3625,7 +3389,7 @@
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to FINISHING: " + r);
r.state = ActivityState.FINISHING;
final boolean finishingActivityInNonFocusedStack
- = r.task.stack != mStackSupervisor.getFocusedStack()
+ = r.getStack() != mStackSupervisor.getFocusedStack()
&& prevState == ActivityState.PAUSED && mode == FINISH_AFTER_VISIBLE;
if (mode == FINISH_IMMEDIATELY
@@ -3806,8 +3570,7 @@
*
* Note: Call before #removeActivityFromHistoryLocked.
*/
- final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices,
- boolean setState) {
+ private void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices, boolean setState) {
if (mResumedActivity == r) {
mResumedActivity = null;
}
@@ -3897,7 +3660,7 @@
/**
* Perform clean-up of service connections in an activity record.
*/
- final void cleanUpActivityServicesLocked(ActivityRecord r) {
+ private void cleanUpActivityServicesLocked(ActivityRecord r) {
// Throw away any services that have been bound by this activity.
if (r.connections != null) {
Iterator<ConnectionRecord> it = r.connections.iterator();
@@ -3915,7 +3678,7 @@
mHandler.sendMessage(msg);
}
- final void destroyActivitiesLocked(ProcessRecord owner, String reason) {
+ private void destroyActivitiesLocked(ProcessRecord owner, String reason) {
boolean lastIsOpaque = false;
boolean activityRemoved = false;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -4192,7 +3955,7 @@
}
}
- boolean removeHistoryRecordsForAppLocked(ProcessRecord app) {
+ private boolean removeHistoryRecordsForAppLocked(ProcessRecord app) {
removeHistoryRecordsForAppLocked(mLRUActivities, app, "mLRUActivities");
removeHistoryRecordsForAppLocked(mStackSupervisor.mStoppingActivities, app,
"mStoppingActivities");
@@ -4283,7 +4046,7 @@
return hasVisibleActivities;
}
- final void updateTransitLocked(int transit, ActivityOptions options) {
+ private void updateTransitLocked(int transit, ActivityOptions options) {
if (options != null) {
ActivityRecord r = topRunningActivityLocked();
if (r != null && r.state != ActivityState.RESUMED) {
@@ -4295,7 +4058,7 @@
mWindowManager.prepareAppTransition(transit, false);
}
- void updateTaskMovement(TaskRecord task, boolean toFront) {
+ private void updateTaskMovement(TaskRecord task, boolean toFront) {
if (task.isPersistable) {
task.mLastTimeMoved = System.currentTimeMillis();
// Sign is used to keep tasks sorted when persisted. Tasks sent to the bottom most
@@ -4504,8 +4267,7 @@
return true;
}
- static final void logStartActivity(int tag, ActivityRecord r,
- TaskRecord task) {
+ static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) {
final Uri data = r.intent.getData();
final String strData = data != null ? data.toSafeString() : null;
@@ -4534,7 +4296,8 @@
(start.task == task) ? activities.indexOf(start) : activities.size() - 1;
for (; activityIndex >= 0; --activityIndex) {
final ActivityRecord r = activities.get(activityIndex);
- updatedConfig |= ensureActivityConfigurationLocked(r, 0, preserveWindow);
+ updatedConfig |= r.ensureActivityConfigurationLocked(0 /* globalChanges */,
+ preserveWindow);
if (r.fullscreen) {
behindFullscreen = true;
break;
@@ -4577,7 +4340,7 @@
}
}
- mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+ mTmpConfigs.put(task.taskId, task.getOverrideConfiguration());
mTmpBounds.put(task.taskId, task.mBounds);
if (tempTaskInsetBounds != null) {
mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
@@ -4631,245 +4394,6 @@
}
}
- /**
- * Make sure the given activity matches the current configuration. Returns false if the activity
- * had to be destroyed. Returns true if the configuration is the same, or the activity will
- * remain running as-is for whatever reason. Ensures the HistoryRecord is updated with the
- * correct configuration and all other bookkeeping is handled.
- */
- boolean ensureActivityConfigurationLocked(
- ActivityRecord r, int globalChanges, boolean preserveWindow) {
- if (mConfigWillChange) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Skipping config check (will change): " + r);
- return true;
- }
-
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Ensuring correct configuration: " + r);
-
- // Short circuit: if the two configurations are equal (the common case), then there is
- // nothing to do.
- final Configuration newConfig = mService.mGlobalConfiguration;
- r.task.sanitizeOverrideConfiguration(newConfig);
- final Configuration taskConfig = r.task.mOverrideConfig;
- if (r.configuration.equals(newConfig)
- && r.taskConfigOverride.equals(taskConfig)
- && !r.forceNewConfig) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration unchanged in " + r);
- return true;
- }
-
- // We don't worry about activities that are finishing.
- if (r.finishing) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration doesn't matter in finishing " + r);
- r.stopFreezingScreenLocked(false);
- return true;
- }
-
- // Okay we now are going to make this activity have the new config.
- // But then we need to figure out how it needs to deal with that.
- final Configuration oldConfig = r.configuration;
- final Configuration oldTaskOverride = r.taskConfigOverride;
- r.configuration = newConfig;
- r.taskConfigOverride = taskConfig;
-
- int taskChanges = getTaskConfigurationChanges(r, taskConfig, oldTaskOverride);
- final int changes = oldConfig.diff(newConfig) | taskChanges;
- if (changes == 0 && !r.forceNewConfig) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration no differences in " + r);
- // There are no significant differences, so we won't relaunch but should still deliver
- // the new configuration to the client process.
- r.scheduleConfigurationChanged(taskConfig, true);
- return true;
- }
-
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration changes for " + r + " ; taskChanges="
- + Configuration.configurationDiffToString(taskChanges) + ", allChanges="
- + Configuration.configurationDiffToString(changes));
-
- // If the activity isn't currently running, just leave the new
- // configuration and it will pick that up next time it starts.
- if (r.app == null || r.app.thread == null) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration doesn't matter not running " + r);
- r.stopFreezingScreenLocked(false);
- r.forceNewConfig = false;
- return true;
- }
-
- // Figure out how to handle the changes between the configurations.
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Checking to restart " + r.info.name + ": changed=0x"
- + Integer.toHexString(changes) + ", handles=0x"
- + Integer.toHexString(r.info.getRealConfigChanged()) + ", newConfig=" + newConfig
- + ", taskConfig=" + taskConfig);
-
- if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
- // Aha, the activity isn't handling the change, so DIE DIE DIE.
- r.configChangeFlags |= changes;
- r.startFreezingScreenLocked(r.app, globalChanges);
- r.forceNewConfig = false;
- preserveWindow &= isResizeOnlyChange(changes);
- if (r.app == null || r.app.thread == null) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Config is destroying non-running " + r);
- destroyActivityLocked(r, true, "config");
- } else if (r.state == ActivityState.PAUSING) {
- // A little annoying: we are waiting for this activity to finish pausing. Let's not
- // do anything now, but just flag that it needs to be restarted when done pausing.
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Config is skipping already pausing " + r);
- r.deferRelaunchUntilPaused = true;
- r.preserveWindowOnDeferredRelaunch = preserveWindow;
- return true;
- } else if (r.state == ActivityState.RESUMED) {
- // Try to optimize this case: the configuration is changing and we need to restart
- // the top, resumed activity. Instead of doing the normal handshaking, just say
- // "restart!".
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Config is relaunching resumed " + r);
-
- if (DEBUG_STATES && !r.visible) {
- Slog.v(TAG_STATES, "Config is relaunching resumed invisible activity " + r
- + " called by " + Debug.getCallers(4));
- }
-
- relaunchActivityLocked(r, r.configChangeFlags, true, preserveWindow);
- } else {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Config is relaunching non-resumed " + r);
- relaunchActivityLocked(r, r.configChangeFlags, false, preserveWindow);
- }
-
- // All done... tell the caller we weren't able to keep this activity around.
- return false;
- }
-
- // Default case: the activity can handle this new configuration, so hand it over.
- // NOTE: We only forward the task override configuration as the system level configuration
- // changes is always sent to all processes when they happen so it can just use whatever
- // system level configuration it last got.
- r.scheduleConfigurationChanged(taskConfig, true);
- r.stopFreezingScreenLocked(false);
-
- return true;
- }
-
- private int getTaskConfigurationChanges(ActivityRecord record, Configuration taskConfig,
- Configuration oldTaskOverride) {
-
- // If we went from full-screen to non-full-screen, make sure to use the correct
- // configuration task diff, so the diff stays as small as possible.
- if (Configuration.EMPTY.equals(oldTaskOverride)
- && !Configuration.EMPTY.equals(taskConfig)) {
- oldTaskOverride = record.task.extractOverrideConfig(record.configuration);
- }
-
- // Conversely, do the same when going the other direction.
- if (Configuration.EMPTY.equals(taskConfig)
- && !Configuration.EMPTY.equals(oldTaskOverride)) {
- taskConfig = record.task.extractOverrideConfig(record.configuration);
- }
-
- // Determine what has changed. May be nothing, if this is a config
- // that has come back from the app after going idle. In that case
- // we just want to leave the official config object now in the
- // activity and do nothing else.
- int taskChanges = oldTaskOverride.diff(taskConfig, true /* skipUndefined */);
- // We don't want to use size changes if they don't cross boundaries that are important to
- // the app.
- if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
- final boolean crosses = record.crossesHorizontalSizeThreshold(
- oldTaskOverride.screenWidthDp, taskConfig.screenWidthDp)
- || record.crossesVerticalSizeThreshold(
- oldTaskOverride.screenHeightDp, taskConfig.screenHeightDp);
- if (!crosses) {
- taskChanges &= ~CONFIG_SCREEN_SIZE;
- }
- }
- if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
- final int oldSmallest = oldTaskOverride.smallestScreenWidthDp;
- final int newSmallest = taskConfig.smallestScreenWidthDp;
- if (!record.crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
- taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
- }
- }
- return taskChanges;
- }
-
- private static boolean isResizeOnlyChange(int change) {
- return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
- | CONFIG_SCREEN_LAYOUT)) == 0;
- }
-
- private void relaunchActivityLocked(
- ActivityRecord r, int changes, boolean andResume, boolean preserveWindow) {
- if (mService.mSuppressResizeConfigChanges && preserveWindow) {
- r.configChangeFlags = 0;
- return;
- }
-
- List<ResultInfo> results = null;
- List<ReferrerIntent> newIntents = null;
- if (andResume) {
- results = r.results;
- newIntents = r.newIntents;
- }
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
- "Relaunching: " + r + " with results=" + results + " newIntents=" + newIntents
- + " andResume=" + andResume + " preserveWindow=" + preserveWindow);
- EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY
- : EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r),
- r.task.taskId, r.shortComponentName);
-
- r.startFreezingScreenLocked(r.app, 0);
-
- mStackSupervisor.removeChildActivityContainers(r);
-
- try {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
- "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r
- + " callers=" + Debug.getCallers(6));
- r.forceNewConfig = false;
- mStackSupervisor.activityRelaunchingLocked(r);
- r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
- !andResume, new Configuration(mService.mGlobalConfiguration),
- new Configuration(r.task.mOverrideConfig), preserveWindow);
- // Note: don't need to call pauseIfSleepingLocked() here, because
- // the caller will only pass in 'andResume' if this activity is
- // currently resumed, which implies we aren't sleeping.
- } catch (RemoteException e) {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
- }
-
- if (andResume) {
- if (DEBUG_STATES) {
- Slog.d(TAG_STATES, "Resumed after relaunch " + r);
- }
- r.results = null;
- r.newIntents = null;
- mService.showUnsupportedZoomDialogIfNeededLocked(r);
- mService.showAskCompatModeDialogLocked(r);
- } else {
- mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
- r.state = ActivityState.PAUSED;
- // if the app is relaunched when it's stopped, and we're not resuming,
- // put it back into stopped state.
- if (r.stopped) {
- addToStopping(r, true /* immediate */);
- }
- }
-
- r.configChangeFlags = 0;
- r.deferRelaunchUntilPaused = false;
- r.preserveWindowOnDeferredRelaunch = false;
- }
-
boolean willActivityBeVisibleLocked(IBinder token) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
@@ -5023,13 +4547,13 @@
}
}
- public void unhandledBackLocked() {
+ void unhandledBackLocked() {
final int top = mTaskHistory.size() - 1;
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Performing unhandledBack(): top activity at " + top);
if (top >= 0) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
int activityTop = activities.size() - 1;
- if (activityTop > 0) {
+ if (activityTop >= 0) {
finishActivityLocked(activities.get(activityTop), Activity.RESULT_CANCELED, null,
"unhandled-back", true);
}
@@ -5223,7 +4747,7 @@
}
}
- task.stack = null;
+ task.setStack(null);
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
@@ -5256,7 +4780,7 @@
void addTask(final TaskRecord task, final boolean toTop, String reason) {
final ActivityStack prevStack = preAddTask(task, reason, toTop);
- task.stack = this;
+ task.setStack(this);
if (toTop) {
insertTaskAtTop(task, null);
} else {
@@ -5268,9 +4792,9 @@
void positionTask(final TaskRecord task, int position) {
final ActivityRecord topRunningActivity = task.topRunningActivityLocked();
- final boolean wasResumed = topRunningActivity == task.stack.mResumedActivity;
+ final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
final ActivityStack prevStack = preAddTask(task, "positionTask", !ON_TOP);
- task.stack = this;
+ task.setStack(this);
insertTaskAtPosition(task, position);
postAddTask(task, prevStack);
if (wasResumed) {
@@ -5284,7 +4808,7 @@
}
private ActivityStack preAddTask(TaskRecord task, String reason, boolean toTop) {
- final ActivityStack prevStack = task.stack;
+ final ActivityStack prevStack = task.getStack();
if (prevStack != null && prevStack != this) {
prevStack.removeTask(task, reason,
toTop ? REMOVE_TASK_MODE_MOVING_TO_TOP : REMOVE_TASK_MODE_MOVING);
@@ -5309,10 +4833,11 @@
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges,
- task.voiceSession != null, r.mLaunchTaskBehind, bounds, task.mOverrideConfig,
- task.mResizeMode, r.isAlwaysFocusable(), task.isHomeTask(),
- r.appInfo.targetSdkVersion, r.mRotationAnimationHint, task.isOnTopLauncher());
- r.taskConfigOverride = task.mOverrideConfig;
+ task.voiceSession != null, r.mLaunchTaskBehind, bounds,
+ task.getOverrideConfiguration(), task.mResizeMode, r.isAlwaysFocusable(),
+ task.isHomeTask(), r.appInfo.targetSdkVersion, r.mRotationAnimationHint,
+ task.isOnTopLauncher());
+ r.onOverrideConfigurationSent();
}
void moveToFrontAndResumeStateIfNeeded(
@@ -5338,7 +4863,7 @@
* created on this stack which the activity is added to.
* */
void moveActivityToStack(ActivityRecord r) {
- final ActivityStack prevStack = r.task.stack;
+ final ActivityStack prevStack = r.getStack();
if (prevStack.mStackId == mStackId) {
// You are already in the right stack silly...
return;
@@ -5363,9 +4888,10 @@
private void setAppTask(ActivityRecord r, TaskRecord task) {
final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds();
- mWindowManager.setAppTask(r.appToken, task.taskId, mStackId, bounds, task.mOverrideConfig,
- task.mResizeMode, task.isHomeTask(), task.isOnTopLauncher());
- r.taskConfigOverride = task.mOverrideConfig;
+ mWindowManager.setAppTask(r.appToken, task.taskId, mStackId, bounds,
+ task.getOverrideConfiguration(), task.mResizeMode, task.isHomeTask(),
+ task.isOnTopLauncher());
+ r.onOverrideConfigurationSent();
}
public int getStackId() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 67604d3..99c5b06 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -45,6 +45,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -76,6 +77,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -178,7 +180,8 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
-public final class ActivityStackSupervisor implements DisplayListener {
+public final class ActivityStackSupervisor extends ConfigurationContainer
+ implements DisplayListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
@@ -412,6 +415,39 @@
private final ResizeDockedStackTimeout mResizeDockedStackTimeout;
+ @Override
+ protected int getChildCount() {
+ return mActivityDisplays.size();
+ }
+
+ @Override
+ protected ActivityDisplay getChildAt(int index) {
+ return mActivityDisplays.valueAt(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ 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;
@@ -571,7 +607,7 @@
final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
if (parent != null) {
- stack = parent.task.stack;
+ stack = parent.getStack();
}
return stack == mFocusedStack;
}
@@ -584,7 +620,7 @@
final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
if (parent != null) {
- stack = parent.task.stack;
+ stack = parent.getStack();
}
return stack == mHomeStack.mStacks.get((mHomeStack.mStacks.size() - 1));
}
@@ -1174,20 +1210,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.mGlobalConfiguration,
- 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;
@@ -1210,7 +1246,7 @@
setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "mLockTaskAuth==LAUNCHABLE", false);
}
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = task.getStack();
try {
if (app.thread == null) {
throw new RemoteException();
@@ -1272,12 +1308,16 @@
app.pendingUiClean = true;
}
app.forceProcessStateUpTo(mService.mTopProcessState);
+ // Because we could be starting an Activity in the system process this may not go across
+ // a Binder interface which would create a new Configuration. Consequently we have to
+ // always create a new Configuration here.
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
- new Configuration(mService.mGlobalConfiguration),
- new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
- task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
- newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
+ new Configuration(mService.getGlobalConfiguration()),
+ new Configuration(task.getMergedOverrideConfiguration()), r.compat,
+ r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
+ r.persistentState, results, newIntents, !andResume,
+ mService.isNextTransitionForward(), profilerInfo);
if ((app.info.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
@@ -1359,7 +1399,7 @@
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
- r.task.stack.setLaunchTime(r);
+ r.getStack().setLaunchTime(r);
if (app != null && app.thread != null) {
try {
@@ -1599,7 +1639,7 @@
// We'll update with whatever configuration it now says
// it used to launch.
if (config != null) {
- r.configuration = config;
+ r.setLastReportedConfiguration(config);
}
// We are now idle. If someone is waiting for a thumbnail from
@@ -1607,7 +1647,7 @@
r.idle = true;
//Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
- if (isFocusedStack(r.task.stack) || fromTimeout) {
+ if (isFocusedStack(r.getStack()) || fromTimeout) {
booting = checkFinishBootingLocked();
}
}
@@ -1645,7 +1685,7 @@
// waiting for the next one to start.
for (int i = 0; i < NS; i++) {
r = stops.get(i);
- final ActivityStack stack = r.task.stack;
+ final ActivityStack stack = r.getStack();
if (stack != null) {
if (r.finishing) {
stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
@@ -1659,7 +1699,7 @@
// waiting for the next one to start.
for (int i = 0; i < NF; i++) {
r = finishes.get(i);
- final ActivityStack stack = r.task.stack;
+ final ActivityStack stack = r.getStack();
if (stack != null) {
activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
}
@@ -1835,7 +1875,8 @@
// we'll just indicate that this task returns to the home task.
task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
}
- if (task.stack == null) {
+ ActivityStack currentStack = task.getStack();
+ if (currentStack == null) {
Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
+ task + " to front. Stack is null");
return;
@@ -1849,10 +1890,10 @@
if (stackId == INVALID_STACK_ID) {
stackId = task.getLaunchStackId();
}
- if (stackId != task.stack.mStackId) {
- final ActivityStack stack = moveTaskToStackUncheckedLocked(
- task, stackId, ON_TOP, !FORCE_FOCUS, reason);
- stackId = stack.mStackId;
+ if (stackId != currentStack.mStackId) {
+ currentStack = moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
+ !FORCE_FOCUS, reason);
+ stackId = currentStack.mStackId;
// moveTaskToStackUncheckedLocked() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
}
@@ -1864,20 +1905,21 @@
// WM resizeTask must be done after the task is moved to the correct stack,
// because Task's setBounds() also updates dim layer's bounds, but that has
// dependency on the stack.
- mWindowManager.resizeTask(task.taskId, task.mBounds, task.mOverrideConfig,
- false /* relayout */, false /* forced */);
+ mWindowManager.resizeTask(task.taskId, task.mBounds,
+ task.getOverrideConfiguration(), false /* relayout */,
+ false /* forced */);
}
}
}
final ActivityRecord r = task.getTopActivity();
- task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+ currentStack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
r == null ? null : r.appTimeTracker, reason);
if (DEBUG_STACK) Slog.d(TAG_STACK,
- "findTaskToMoveToFront: moved to front of stack=" + task.stack);
+ "findTaskToMoveToFront: moved to front of stack=" + currentStack);
- handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.stack.mStackId,
+ handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, currentStack.mStackId,
forceNonResizeable);
}
@@ -1967,15 +2009,10 @@
}
}
- void deleteActivityContainer(IActivityContainer container) {
- ActivityContainer activityContainer = (ActivityContainer)container;
- if (activityContainer != null) {
- if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS,
- "deleteActivityContainer: callers=" + Debug.getCallers(4));
- final int stackId = activityContainer.mStackId;
- mActivityContainers.remove(stackId);
- mWindowManager.removeStack(stackId);
- }
+ void deleteActivityContainerRecord(int stackId) {
+ if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS,
+ "deleteActivityContainerRecord: callers=" + Debug.getCallers(4));
+ mActivityContainers.remove(stackId);
}
void resizeStackLocked(int stackId, Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
@@ -2212,7 +2249,7 @@
// All we can do for now is update the bounds so it can be used when the task is
// added to window manager.
task.updateOverrideConfiguration(bounds);
- if (task.stack != null && task.stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
+ if (task.getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
// re-restore the task so it can have the proper stack association.
restoreRecentTaskLocked(task, FREEFORM_WORKSPACE_STACK_ID);
}
@@ -2233,11 +2270,9 @@
if (updatedConfig) {
final ActivityRecord r = task.topRunningActivityLocked();
if (r != null) {
- final ActivityStack stack = task.stack;
- kept = stack.ensureActivityConfigurationLocked(r, 0, preserveWindow);
+ kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */, preserveWindow);
if (!deferResume) {
-
// All other activities must be made visible with their correct configuration.
ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
if (!kept) {
@@ -2246,7 +2281,8 @@
}
}
}
- mWindowManager.resizeTask(task.taskId, task.mBounds, task.mOverrideConfig, kept, forced);
+ mWindowManager.resizeTask(task.taskId, task.mBounds, task.getOverrideConfiguration(), kept,
+ forced);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
return kept;
@@ -2291,15 +2327,16 @@
stackId = FULLSCREEN_WORKSPACE_STACK_ID;
}
- if (task.stack != null) {
+ final ActivityStack currentStack = task.getStack();
+ if (currentStack != null) {
// Task has already been restored once. See if we need to do anything more
- if (task.stack.mStackId == stackId) {
+ if (currentStack.mStackId == stackId) {
// Nothing else to do since it is already restored in the right stack.
return true;
}
// Remove current stack association, so we can re-associate the task with the
// right stack below.
- task.stack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
+ currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
}
final ActivityStack stack =
@@ -2343,7 +2380,7 @@
}
final ActivityRecord r = task.topRunningActivityLocked();
- final ActivityStack prevStack = task.stack;
+ final ActivityStack prevStack = task.getStack();
final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
final boolean wasResumed = prevStack.mResumedActivity == r;
// In some cases the focused stack isn't the front stack. E.g. pinned stack.
@@ -2393,7 +2430,8 @@
return false;
}
- if (task.stack != null && task.stack.mStackId == stackId) {
+ final ActivityStack currentStack = task.getStack();
+ if (currentStack != null && currentStack.mStackId == stackId) {
// You are already in the right stack silly...
Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
return true;
@@ -2405,7 +2443,7 @@
}
final ActivityRecord topActivity = task.getTopActivity();
- final int sourceStackId = task.stack != null ? task.stack.mStackId : INVALID_STACK_ID;
+ final int sourceStackId = task.getStackId();
final boolean mightReplaceWindow =
StackId.replaceWindowsOnTaskMove(sourceStackId, stackId) && topActivity != null;
if (mightReplaceWindow) {
@@ -2505,7 +2543,7 @@
try {
final TaskRecord task = r.task;
- if (r == task.stack.getVisibleBehindActivity()) {
+ if (r == task.getStack().getVisibleBehindActivity()) {
// An activity can't be pinned and visible behind at the same time. Go ahead and
// release it from been visible behind before pinning.
requestVisibleBehindLocked(r, false);
@@ -2555,13 +2593,13 @@
}
final TaskRecord task = r.task;
- if (task == null || task.stack == null) {
+ final ActivityStack stack = r.getStack();
+ if (stack == null) {
Slog.w(TAG, "moveActivityStackToFront: invalid task or stack: r="
+ r + " task=" + task);
return false;
}
- final ActivityStack stack = task.stack;
if (stack == mFocusedStack && stack.topRunningActivityLocked() == r) {
if (DEBUG_FOCUS) Slog.d(TAG_FOCUS,
"moveActivityStackToFront: already on top, r=" + r);
@@ -2586,7 +2624,7 @@
task.updateOverrideConfigurationForStack(stack);
mWindowManager.positionTaskInStack(
- taskId, stackId, position, task.mBounds, task.mOverrideConfig);
+ taskId, stackId, position, task.mBounds, task.getOverrideConfiguration());
stack.positionTask(task, position);
// The task might have already been running and its visibility needs to be synchronized with
// the visibility of the stack / windows.
@@ -2771,7 +2809,7 @@
}
boolean reportResumedActivityLocked(ActivityRecord r) {
- final ActivityStack stack = r.task.stack;
+ final ActivityStack stack = r.getStack();
if (isFocusedStack(stack)) {
mService.updateUsageStats(r, true);
}
@@ -2795,7 +2833,7 @@
}
boolean requestVisibleBehindLocked(ActivityRecord r, boolean visible) {
- final ActivityStack stack = r.task.stack;
+ final ActivityStack stack = r.getStack();
if (stack == null) {
if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND,
"requestVisibleBehind: r=" + r + " visible=" + visible + " stack is null");
@@ -2859,12 +2897,12 @@
}
// Called when WindowManager has finished animating the launchingBehind activity to the back.
- void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
+ private void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
final TaskRecord task = r.task;
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = task.getStack();
r.mLaunchTaskBehind = false;
- task.setLastThumbnailLocked(stack.screenshotActivitiesLocked(r));
+ task.setLastThumbnailLocked(r.screenshotActivityLocked());
mRecentTasks.addLocked(task);
mService.notifyTaskStackChangedLocked();
mWindowManager.setAppVisibility(r.appToken, false);
@@ -3133,6 +3171,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.
*/
@@ -3301,8 +3353,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);
@@ -3371,6 +3422,18 @@
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
}
+ DisplayMetrics getDisplayRealMetrics(int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ activityDisplay.mDisplay.getRealMetrics(activityDisplay.mRealMetrics);
+ return activityDisplay.mRealMetrics;
+ }
+
+ Point getDisplayRealSize(int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ activityDisplay.mDisplay.getRealSize(activityDisplay.mRealSize);
+ return activityDisplay.mRealSize;
+ }
+
private void handleDisplayAdded(int displayId) {
boolean newDisplay;
synchronized (mService) {
@@ -3402,7 +3465,7 @@
if (activityDisplay != null) {
ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stacks.get(stackNdx).mActivityContainer.detachLocked();
+ stacks.get(stackNdx).mActivityContainer.removeLocked();
}
mActivityDisplays.remove(displayId);
}
@@ -3606,7 +3669,7 @@
lockTaskModeState != LOCK_TASK_MODE_NONE);
resumeFocusedStackTopActivityLocked();
} else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
- handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.stack.mStackId,
+ handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.getStackId(),
true /* forceNonResizable */);
}
}
@@ -3715,7 +3778,7 @@
}
void scheduleReportPictureInPictureModeChangedIfNeeded(TaskRecord task, ActivityStack prevStack) {
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = task.getStack();
if (prevStack == null || prevStack == stack
|| (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) {
return;
@@ -4042,14 +4105,14 @@
}
}
- protected void detachLocked() {
- if (DEBUG_STACK) Slog.d(TAG_STACK, "detachLocked: " + this + " from display="
+ void removeLocked() {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "removeLocked: " + this + " from display="
+ mActivityDisplay + " Callers=" + Debug.getCallers(2));
if (mActivityDisplay != null) {
mActivityDisplay.detachActivitiesLocked(mStack);
mActivityDisplay = null;
- mStack.detachDisplay();
}
+ mStack.remove();
}
@Override
@@ -4124,8 +4187,7 @@
}
void onTaskListEmptyLocked() {
- detachLocked();
- deleteActivityContainer(this);
+ removeLocked();
mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget();
}
@@ -4227,11 +4289,12 @@
/** Exactly one of these classes per Display in the system. Capable of holding zero or more
* attached {@link ActivityStack}s */
- class ActivityDisplay {
+ class ActivityDisplay extends ConfigurationContainer {
/** Actual Display this object tracks. */
int mDisplayId;
Display mDisplay;
- DisplayInfo mDisplayInfo = new DisplayInfo();
+ private final DisplayMetrics mRealMetrics = new DisplayMetrics();
+ private final Point mRealSize = new Point();
/** All of the stacks on this display. Order matters, topmost stack is in front of all other
* stacks, bottommost behind. Accessed directly by ActivityManager package classes */
@@ -4255,7 +4318,6 @@
void init(Display display) {
mDisplay = display;
mDisplayId = display.getDisplayId();
- mDisplay.getDisplayInfo(mDisplayInfo);
}
void attachActivities(ActivityStack stack, boolean onTop) {
@@ -4287,6 +4349,21 @@
public String toString() {
return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
}
+
+ @Override
+ protected int getChildCount() {
+ return mStacks.size();
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return mStacks.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return ActivityStackSupervisor.this;
+ }
}
class VirtualActivityDisplay extends ActivityDisplay {
@@ -4392,7 +4469,7 @@
focusedStack != null ? focusedStack.topActivity() : null;
if (launchStackId != INVALID_STACK_ID) {
- if (task.stack.mStackId != launchStackId) {
+ if (task.getStackId() != launchStackId) {
moveTaskToStackLocked(
taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
ANIMATE);
@@ -4418,8 +4495,8 @@
mService.mActivityStarter.postStartActivityUncheckedProcessing(task.getTopActivity(),
ActivityManager.START_TASK_TO_FRONT,
- sourceRecord != null ? sourceRecord.task.stack.mStackId : INVALID_STACK_ID,
- sourceRecord, task.stack);
+ sourceRecord != null ? sourceRecord.task.getStackId() : INVALID_STACK_ID,
+ sourceRecord, task.getStack());
return ActivityManager.START_TASK_TO_FRONT;
}
callingUid = task.mCallingUid;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 5feaf1f..20a14d3 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -98,6 +98,7 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -360,7 +361,7 @@
}
}
- final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
+ final ActivityStack resultStack = resultRecord == null ? null : resultRecord.getStack();
if (err != START_SUCCESS) {
if (resultRecord != null) {
@@ -469,9 +470,9 @@
}
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
- intent, resolvedType, aInfo, mService.mGlobalConfiguration, resultRecord, resultWho,
- requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
- options, sourceRecord);
+ intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord,
+ resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor,
+ container, options, sourceRecord);
if (outActivity != null) {
outActivity[0] = r;
}
@@ -591,8 +592,9 @@
}
int startedActivityStackId = INVALID_STACK_ID;
- if (r.task != null && r.task.stack != null) {
- startedActivityStackId = r.task.stack.mStackId;
+ final ActivityStack currentStack = r.getStack();
+ if (currentStack != null) {
+ startedActivityStackId = currentStack.mStackId;
} else if (mTargetStack != null) {
startedActivityStackId = targetStack.mStackId;
}
@@ -780,7 +782,7 @@
stack = container.mStack;
}
stack.mConfigWillChange = globalConfig != null
- && mService.mGlobalConfiguration.diff(globalConfig) != 0;
+ && mService.getGlobalConfiguration().diff(globalConfig) != 0;
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Starting activity when config will change = " + stack.mConfigWillChange);
@@ -1002,7 +1004,7 @@
curTop.task != null && mStartActivity != null &&
curTop.task != mStartActivity.task )) &&
mService.mLocalPowerManager != null) {
- mService.mLocalPowerManager.powerHint(PowerManagerInternal.POWER_HINT_LAUNCH, 1);
+ mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 1);
mPowerHintSent = true;
}
}
@@ -1010,7 +1012,7 @@
void sendPowerHintForLaunchEndIfNeeded() {
// Trigger launch power hint if activity is launched
if (mPowerHintSent && mService.mLocalPowerManager != null) {
- mService.mLocalPowerManager.powerHint(PowerManagerInternal.POWER_HINT_LAUNCH, 0);
+ mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 0);
mPowerHintSent = false;
}
}
@@ -1099,10 +1101,12 @@
}
if (mStartActivity.packageName == null) {
- if (mStartActivity.resultTo != null && mStartActivity.resultTo.task.stack != null) {
- mStartActivity.resultTo.task.stack.sendActivityResultLocked(
- -1, mStartActivity.resultTo, mStartActivity.resultWho,
- mStartActivity.requestCode, RESULT_CANCELED, null);
+ final ActivityStack sourceStack = mStartActivity.resultTo != null
+ ? mStartActivity.resultTo.getStack() : null;
+ if (sourceStack != null) {
+ sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
+ mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
+ null /* data */);
}
ActivityOptions.abort(mOptions);
return START_CLASS_NOT_FOUND;
@@ -1312,15 +1316,17 @@
}
private void sendNewTaskResultRequestIfNeeded() {
- if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
- && mStartActivity.resultTo.task.stack != null) {
+ final ActivityStack sourceStack = mStartActivity.resultTo != null
+ ? mStartActivity.resultTo.getStack() : null;
+ if (sourceStack != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new task...
// yet the caller has requested a result back. Well, that is pretty messed up,
// so instead immediately send back a cancel and let the new task continue launched
// as normal without a dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
- mStartActivity.resultTo.task.stack.sendActivityResultLocked(-1, mStartActivity.resultTo,
- mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null);
+ sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
+ mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
+ null /* data */);
mStartActivity.resultTo = null;
}
}
@@ -1328,7 +1334,7 @@
private void computeLaunchingTaskFlags() {
// If the caller is not coming from another activity, but has given us an explicit task into
// which they would like us to launch the new activity, then let's see about doing that.
- if (mSourceRecord == null && mInTask != null && mInTask.stack != null) {
+ if (mSourceRecord == null && mInTask != null && mInTask.getStack() != null) {
final Intent baseIntent = mInTask.getBaseIntent();
final ActivityRecord root = mInTask.getRootActivity();
if (baseIntent == null) {
@@ -1415,7 +1421,7 @@
return;
}
if (!mSourceRecord.finishing) {
- mSourceStack = mSourceRecord.task.stack;
+ mSourceStack = mSourceRecord.getStack();
return;
}
@@ -1474,7 +1480,7 @@
}
private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
- mTargetStack = intentActivity.task.stack;
+ mTargetStack = intentActivity.getStack();
mTargetStack.mLastPausedActivity = null;
// If the target task is not in the front, then we need to bring it to the front...
// except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
@@ -1586,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.
@@ -1606,7 +1612,7 @@
// is put in the right place.
mSourceRecord = intentActivity;
final TaskRecord task = mSourceRecord.task;
- if (task != null && task.stack == null) {
+ if (task != null && task.getStack() == null) {
// Target stack got cleared when we all activities were removed above.
// Go ahead and reset it.
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
@@ -1722,18 +1728,19 @@
}
final TaskRecord sourceTask = mSourceRecord.task;
+ final ActivityStack sourceStack = mSourceRecord.getStack();
// We only want to allow changing stack if the target task is not the top one,
// otherwise we would move the launching task to the other side, rather than show
// two side by side.
- final boolean moveStackAllowed = sourceTask.stack.topTask() != sourceTask;
+ final boolean moveStackAllowed = sourceStack.topTask() != sourceTask;
if (moveStackAllowed) {
mTargetStack = getLaunchStack(mStartActivity, mLaunchFlags, mStartActivity.task,
mOptions);
}
if (mTargetStack == null) {
- mTargetStack = sourceTask.stack;
- } else if (mTargetStack != sourceTask.stack) {
+ mTargetStack = sourceStack;
+ } else if (mTargetStack != sourceStack) {
mSupervisor.moveTaskToStackLocked(sourceTask.taskId, mTargetStack.mStackId,
ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE);
}
@@ -1800,7 +1807,7 @@
if (mLaunchBounds != null) {
mInTask.updateOverrideConfiguration(mLaunchBounds);
int stackId = mInTask.getLaunchStackId();
- if (stackId != mInTask.stack.mStackId) {
+ if (stackId != mInTask.getStackId()) {
final ActivityStack stack = mSupervisor.moveTaskToStackUncheckedLocked(
mInTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
stackId = stack.mStackId;
@@ -1809,7 +1816,7 @@
mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
}
}
- mTargetStack = mInTask.stack;
+ mTargetStack = mInTask.getStack();
mTargetStack.moveTaskToFrontLocked(
mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
@@ -1916,10 +1923,10 @@
return stack;
}
- if (task != null && task.stack != null) {
- stack = task.stack;
- if (stack.isOnHomeDisplay()) {
- if (mSupervisor.mFocusedStack != stack) {
+ final ActivityStack currentStack = task != null ? task.getStack() : null;
+ if (currentStack != null) {
+ if (currentStack.isOnHomeDisplay()) {
+ if (mSupervisor.mFocusedStack != currentStack) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Setting " + "focused stack to r=" + r
+ " task=" + task);
@@ -1929,7 +1936,7 @@
+ mSupervisor.mFocusedStack);
}
}
- return stack;
+ return currentStack;
}
final ActivityStackSupervisor.ActivityContainer container = r.mInitialActivityContainer;
@@ -1980,7 +1987,7 @@
// We are reusing a task, keep the stack!
if (mReuseTask != null) {
- return mReuseTask.stack;
+ return mReuseTask.getStack();
}
final int launchStackId =
@@ -2001,7 +2008,7 @@
// The parent activity doesn't want to launch the activity on top of itself, but
// instead tries to put it onto other side in side-by-side mode.
- final ActivityStack parentStack = task != null ? task.stack
+ final ActivityStack parentStack = task != null ? task.getStack()
: r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack
: mSupervisor.mFocusedStack;
@@ -2091,4 +2098,18 @@
return (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
(flags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
}
+
+ boolean clearPendingActivityLaunchesLocked(String packageName) {
+ boolean didSomething = false;
+
+ for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
+ PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
+ ActivityRecord r = pal.r;
+ if (r != null && r.packageName.equals(packageName)) {
+ mPendingActivityLaunches.remove(palNdx);
+ didSomething = true;
+ }
+ }
+ return didSomething;
+ }
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 3b4b8d6..4e69162 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -228,7 +228,8 @@
public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
final Intent intent = r.intent;
for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
+ final Intent curIntent = mParallelBroadcasts.get(i).intent;
+ if (intent.filterEquals(curIntent)) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"***** DROPPING PARALLEL ["
+ mQueueName + "]: " + intent);
@@ -267,7 +268,7 @@
r.receiver = app.thread.asBinder();
r.curApp = app;
- app.curReceiver = r;
+ app.curReceivers.add(r);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
mService.updateLruProcessLocked(app, false, null);
mService.updateOomAdjLocked();
@@ -295,7 +296,7 @@
"Process cur broadcast " + r + ": NOT STARTED!");
r.receiver = null;
r.curApp = null;
- app.curReceiver = null;
+ app.curReceivers.remove(r);
}
}
}
@@ -396,8 +397,8 @@
}
r.receiver = null;
r.intent.setComponent(null);
- if (r.curApp != null && r.curApp.curReceiver == r) {
- r.curApp.curReceiver = null;
+ if (r.curApp != null && r.curApp.curReceivers.contains(r)) {
+ r.curApp.curReceivers.remove(r);
}
if (r.curFilter != null) {
r.curFilter.receiverList.curBroadcast = null;
@@ -650,7 +651,7 @@
// things that directly call the IActivityManager API, which
// are already core system stuff so don't matter for this.
r.curApp = filter.receiverList.app;
- filter.receiverList.app.curReceiver = r;
+ filter.receiverList.app.curReceivers.add(r);
mService.updateOomAdjLocked(r.curApp);
}
}
@@ -678,7 +679,7 @@
r.curFilter = null;
filter.receiverList.curBroadcast = null;
if (filter.receiverList.app != null) {
- filter.receiverList.app.curReceiver = null;
+ filter.receiverList.app.curReceivers.remove(r);
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 3437ae6..1e7911a 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -219,6 +219,9 @@
int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized,
boolean _sticky, boolean _initialSticky,
int _userId) {
+ if (_intent == null) {
+ throw new NullPointerException("Can't construct with a null intent");
+ }
queue = _queue;
intent = _intent;
targetComp = _intent.getComponent();
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index 0b282ed..bfc0456 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -38,6 +38,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -197,18 +198,19 @@
}
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
- CompatibilityInfo ci = new CompatibilityInfo(ai, mService.mGlobalConfiguration.screenLayout,
- mService.mGlobalConfiguration.smallestScreenWidthDp,
+ final Configuration globalConfig = mService.getGlobalConfiguration();
+ CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
+ globalConfig.smallestScreenWidthDp,
(getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
//Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
return ci;
}
public int computeCompatModeLocked(ApplicationInfo ai) {
- boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
- CompatibilityInfo info = new CompatibilityInfo(ai,
- mService.mGlobalConfiguration.screenLayout,
- mService.mGlobalConfiguration.smallestScreenWidthDp, enabled);
+ final boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
+ final Configuration globalConfig = mService.getGlobalConfiguration();
+ final CompatibilityInfo info = new CompatibilityInfo(ai, globalConfig.screenLayout,
+ globalConfig.smallestScreenWidthDp, enabled);
if (info.alwaysSupportsScreen()) {
return ActivityManager.COMPAT_MODE_NEVER;
}
@@ -383,7 +385,8 @@
}
if (starting != null) {
- stack.ensureActivityConfigurationLocked(starting, 0, false);
+ starting.ensureActivityConfigurationLocked(0 /* globalChanges */,
+ false /* preserveWindow */);
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
stack.ensureActivitiesVisibleLocked(starting, 0, !PRESERVE_WINDOWS);
@@ -408,8 +411,9 @@
out.startTag(null, "compat-packages");
final IPackageManager pm = AppGlobals.getPackageManager();
- final int screenLayout = mService.mGlobalConfiguration.screenLayout;
- final int smallestScreenWidthDp = mService.mGlobalConfiguration.smallestScreenWidthDp;
+ final Configuration globalConfig = mService.getGlobalConfiguration();
+ final int screenLayout = globalConfig.screenLayout;
+ final int smallestScreenWidthDp = globalConfig.smallestScreenWidthDp;
final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
new file mode 100644
index 0000000..a3e95b8
--- /dev/null
+++ b/services/core/java/com/android/server/am/ConfigurationContainer.java
@@ -0,0 +1,132 @@
+/*
+ * 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.am;
+
+import android.content.res.Configuration;
+
+/**
+ * Contains common logic for classes that have override configurations and are organized in a
+ * hierarchy.
+ */
+abstract class ConfigurationContainer<E extends ConfigurationContainer> {
+
+ /** Contains override configuration settings applied to this configuration container. */
+ private Configuration mOverrideConfiguration = new Configuration();
+
+ /**
+ * Contains full configuration applied to this configuration container. Corresponds to full
+ * parent's config with applied {@link #mOverrideConfiguration}.
+ */
+ private Configuration mFullConfiguration = new Configuration();
+
+ /**
+ * Contains merged override configuration settings from the top of the hierarchy down to this
+ * particular instance. It is different from {@link #mFullConfiguration} because it starts from
+ * topmost container's override config instead of global config.
+ */
+ private Configuration mMergedOverrideConfiguration = new Configuration();
+
+ /**
+ * Returns full configuration applied to this configuration container.
+ * This method should be used for getting settings applied in each particular level of the
+ * hierarchy.
+ */
+ Configuration getConfiguration() {
+ return mFullConfiguration;
+ }
+
+ /**
+ * Notify that parent config changed and we need to update full configuration.
+ * @see #mFullConfiguration
+ */
+ void onConfigurationChanged(Configuration newParentConfig) {
+ mFullConfiguration.setTo(newParentConfig);
+ mFullConfiguration.updateFrom(mOverrideConfiguration);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ConfigurationContainer child = getChildAt(i);
+ child.onConfigurationChanged(mFullConfiguration);
+ }
+ }
+
+ /** Returns override configuration applied to this configuration container. */
+ Configuration getOverrideConfiguration() {
+ return mOverrideConfiguration;
+ }
+
+ /**
+ * Update override configuration and recalculate full config.
+ * @see #mOverrideConfiguration
+ * @see #mFullConfiguration
+ */
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ mOverrideConfiguration.setTo(overrideConfiguration);
+ // Update full configuration of this container and all its children.
+ final ConfigurationContainer parent = getParent();
+ onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
+ // Update merged override config of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
+
+ /**
+ * Get merged override configuration from the top of the hierarchy down to this particular
+ * instance. This should be reported to client as override config.
+ */
+ Configuration getMergedOverrideConfiguration() {
+ return mMergedOverrideConfiguration;
+ }
+
+ /**
+ * Update merged override configuration based on corresponding parent's config and notify all
+ * its children. If there is no parent, merged override configuration will set equal to current
+ * override config.
+ * @see #mMergedOverrideConfiguration
+ */
+ private void onMergedOverrideConfigurationChanged() {
+ final ConfigurationContainer parent = getParent();
+ if (parent != null) {
+ mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
+ mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
+ } else {
+ mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
+ }
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ConfigurationContainer child = getChildAt(i);
+ child.onMergedOverrideConfigurationChanged();
+ }
+ }
+
+ /**
+ * Must be called when new parent for the container was set.
+ */
+ void onParentChanged() {
+ final ConfigurationContainer parent = getParent();
+ // 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();
+
+ abstract protected E getChildAt(int index);
+
+ abstract protected ConfigurationContainer getParent();
+}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 9c08453..c494171 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -296,9 +296,10 @@
}
break;
case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
- if (key.activity.task.stack != null) {
- key.activity.task.stack.sendActivityResultLocked(-1, key.activity,
- key.who, key.requestCode, code, finalIntent);
+ final ActivityStack stack = key.activity.getStack();
+ if (stack != null) {
+ stack.sendActivityResultLocked(-1, key.activity, key.who,
+ key.requestCode, code, finalIntent);
}
break;
case ActivityManager.INTENT_SENDER_BROADCAST:
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3fffefb..49fe79c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -143,7 +143,7 @@
Bundle instrumentationArguments;// as given to us
ComponentName instrumentationResultClass;// copy of instrumentationClass
boolean usingWrapper; // Set to true when process was launched with a wrapper attached
- BroadcastRecord curReceiver;// receiver currently running in the app
+ final ArraySet<BroadcastRecord> curReceivers = new ArraySet<BroadcastRecord>();// receivers currently running in the app
long lastWakeTime; // How long proc held wake lock at last check
long lastCpuTime; // How long proc has run CPU at last check
long curCpuTime; // How long proc has run CPU most recently
@@ -427,8 +427,11 @@
pw.print(prefix); pw.print(" - "); pw.println(conProviders.get(i).toShortString());
}
}
- if (curReceiver != null) {
- pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver);
+ if (!curReceivers.isEmpty()) {
+ pw.print(prefix); pw.println("Current Receivers:");
+ for (int i=0; i < curReceivers.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(curReceivers.valueAt(i));
+ }
}
if (receivers.size() > 0) {
pw.print(prefix); pw.println("Receivers:");
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/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index beb863b..bc9bda2f 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -129,7 +129,8 @@
}
void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
- if (task != null && task.stack != null && task.stack.isHomeStack()) {
+ final ActivityStack stack = task != null ? task.getStack() : null;
+ if (stack != null && stack.isHomeStack()) {
// Never persist the home stack.
return;
}
@@ -147,8 +148,9 @@
}
}
for (int i = size() - 1; i >= 0; i--) {
- TaskRecord task = get(i);
- if (task.isPersistable && (task.stack == null || !task.stack.isHomeStack())) {
+ final TaskRecord task = get(i);
+ final ActivityStack stack = task.getStack();
+ if (task.isPersistable && (stack == null || !stack.isHomeStack())) {
// Set of persisted taskIds for task.userId should not be null here
// TODO Investigate why it can happen. For now initialize with an empty set
if (mPersistedTaskIds.get(task.userId) == null) {
@@ -618,10 +620,12 @@
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
int maxRecents = task.maxRecents - 1;
+ final ActivityStack stack = task.getStack();
for (int i = 0; i < recentsCount; i++) {
final TaskRecord tr = get(i);
+ final ActivityStack trStack = tr.getStack();
if (task != tr) {
- if (task.stack != null && tr.stack != null && task.stack != tr.stack) {
+ if (stack != null && trStack != null && stack != trStack) {
continue;
}
if (task.userId != tr.userId) {
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 43eb251..1ecb2e9 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -54,6 +54,8 @@
import java.util.Comparator;
import java.util.List;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+
public class TaskPersister {
static final String TAG = "TaskPersister";
static final boolean DEBUG = false;
@@ -450,7 +452,7 @@
final int taskId = task.taskId;
if (mStackSupervisor.anyTaskForIdLocked(taskId,
- /* restoreFromRecents= */ false, 0) != null) {
+ /* restoreFromRecents= */ false, HOME_STACK_ID) != null) {
// Should not happen.
Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
} else if (userId != task.userId) {
@@ -639,8 +641,9 @@
final TaskRecord task = mRecentTasks.get(taskNdx);
if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
" persistable=" + task.isPersistable);
+ final ActivityStack stack = task.getStack();
if ((task.isPersistable || task.inRecents)
- && (task.stack == null || !task.stack.isHomeStack())) {
+ && (stack == null || !stack.isHomeStack())) {
if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
persistentTaskIds.add(task.taskId);
} else {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 6cc4d73..9d97dd3 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -56,6 +56,8 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
@@ -67,15 +69,15 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
+import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-import static android.content.res.Configuration.SCREENLAYOUT_LONG_MASK;
-import static android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
@@ -93,18 +95,18 @@
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
-final class TaskRecord {
+final class TaskRecord extends ConfigurationContainer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
- static final String ATTR_TASKID = "task_id";
+ private static final String ATTR_TASKID = "task_id";
private static final String TAG_INTENT = "intent";
private static final String TAG_AFFINITYINTENT = "affinity_intent";
- static final String ATTR_REALACTIVITY = "real_activity";
- static final String ATTR_REALACTIVITY_SUSPENDED = "real_activity_suspended";
+ private static final String ATTR_REALACTIVITY = "real_activity";
+ private static final String ATTR_REALACTIVITY_SUSPENDED = "real_activity_suspended";
private static final String ATTR_ORIGACTIVITY = "orig_activity";
private static final String TAG_ACTIVITY = "activity";
private static final String ATTR_AFFINITY = "affinity";
@@ -121,7 +123,7 @@
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
- static final String ATTR_TASK_AFFILIATION = "task_affiliation";
+ private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
@@ -132,12 +134,15 @@
private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
private static final String ATTR_MIN_WIDTH = "min_width";
private static final String ATTR_MIN_HEIGHT = "min_height";
+ private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version";
-
+ // Current version of the task record we persist. Used to check if we need to run any upgrade
+ // code.
+ private static final int PERSIST_TASK_VERSION = 1;
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
static final int INVALID_TASK_ID = -1;
- static final int INVALID_MIN_SIZE = -1;
+ private static final int INVALID_MIN_SIZE = -1;
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
@@ -173,8 +178,8 @@
// Based on the {@link ActivityInfo#resizeMode} of the root activity.
boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
// changes on a temporary basis.
- int mLockTaskMode; // Which tasklock mode to launch this task in. One of
- // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
+ private int mLockTaskMode; // Which tasklock mode to launch this task in. One of
+ // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
private boolean mPrivileged; // The root activity application of this task holds
// privileged permissions.
private boolean mIsOnTopLauncher; // Whether this task is an on-top launcher. See
@@ -202,8 +207,8 @@
/** List of all activities in the task arranged in history order */
final ArrayList<ActivityRecord> mActivities;
- /** Current stack */
- ActivityStack stack;
+ /** Current stack. Setter must always be used to update the value. */
+ private ActivityStack mStack;
/** Takes on same set of values as ActivityRecord.mActivityType */
int taskType;
@@ -223,7 +228,7 @@
private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE;
/** If original intent did not allow relinquishing task identity, save that information */
- boolean mNeverRelinquishIdentity = true;
+ private boolean mNeverRelinquishIdentity = true;
// Used in the unique case where we are clearing the task in order to reuse it. In that case we
// do not want to delete the stack when the task goes empty.
@@ -271,8 +276,8 @@
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = -1;
- /** Contains configurations settings that are different from the parent's configuration. */
- Configuration mOverrideConfig = Configuration.EMPTY;
+ /** Helper object used for updating override configuration. */
+ private Configuration mTmpConfig = new Configuration();
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
@@ -402,7 +407,7 @@
private void setIntent(Intent _intent, ActivityInfo info) {
if (intent == null) {
mNeverRelinquishIdentity =
- (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
} else if (mNeverRelinquishIdentity) {
return;
}
@@ -527,6 +532,38 @@
mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
}
+ ActivityStack getStack() {
+ return mStack;
+ }
+
+ /** Must be used for setting parent stack because it performs configuration updates. */
+ void setStack(ActivityStack stack) {
+ mStack = stack;
+ onParentChanged();
+ }
+
+ /**
+ * @return Id of current stack, {@link INVALID_STACK_ID} if no stack is set.
+ */
+ int getStackId() {
+ return mStack != null ? mStack.mStackId : INVALID_STACK_ID;
+ }
+
+ @Override
+ protected int getChildCount() {
+ return 0;
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return null;
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mStack;
+ }
+
// Close up recents linked list.
void closeRecentsChain() {
if (mPrevAffiliate != null) {
@@ -576,23 +613,26 @@
* @return whether the thumbnail was set
*/
boolean setLastThumbnailLocked(Bitmap thumbnail) {
- final Configuration serviceConfig = mService.mGlobalConfiguration;
int taskWidth = 0;
int taskHeight = 0;
if (mBounds != null) {
// Non-fullscreen tasks
taskWidth = mBounds.width();
taskHeight = mBounds.height();
- } else if (stack != null) {
+ } else if (mStack != null) {
// Fullscreen tasks
final Point displaySize = new Point();
- stack.getDisplaySize(displaySize);
+ mStack.getDisplaySize(displaySize);
taskWidth = displaySize.x;
taskHeight = displaySize.y;
} else {
Slog.e(TAG, "setLastThumbnailLocked() called on Task without stack");
}
- return setLastThumbnailLocked(thumbnail, taskWidth, taskHeight, serviceConfig.orientation);
+ // We need to provide the current orientation of the display on which this task resides,
+ // not the orientation of the task.
+ final int orientation =
+ getStack().mActivityContainer.mActivityDisplay.getConfiguration().orientation;
+ return setLastThumbnailLocked(thumbnail, taskWidth, taskHeight, orientation);
}
/**
@@ -681,7 +721,7 @@
}
ActivityRecord topRunningActivityLocked() {
- if (stack != null) {
+ if (mStack != null) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = mActivities.get(activityNdx);
if (!r.finishing && r.okToShowLocked()) {
@@ -693,7 +733,7 @@
}
ActivityRecord topRunningActivityWithStartingWindowLocked() {
- if (stack != null) {
+ if (mStack != null) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = mActivities.get(activityNdx);
if (r.mStartingWindowState != STARTING_WINDOW_SHOWN
@@ -817,7 +857,7 @@
mService.notifyTaskPersisterLocked(this, false);
}
- if (stack != null && stack.mStackId == PINNED_STACK_ID) {
+ if (getStackId() == PINNED_STACK_ID) {
// We normally notify listeners of task stack changes on pause, however pinned stack
// activities are normally in the paused state so no notification will be sent there
// before the activity is removed. We send it here so instead.
@@ -849,13 +889,13 @@
if (r.finishing) {
continue;
}
- if (stack == null) {
+ if (mStack == null) {
// Task was restored from persistent storage.
r.takeFromHistory();
mActivities.remove(activityNdx);
--activityNdx;
--numActivities;
- } else if (stack.finishActivityLocked(
+ } else if (mStack.finishActivityLocked(
r, Activity.RESULT_CANCELED, null, "clear-task-index", false)) {
--activityNdx;
--numActivities;
@@ -910,7 +950,7 @@
if (opts != null) {
ret.updateOptionsLocked(opts);
}
- if (stack != null && stack.finishActivityLocked(
+ if (mStack != null && mStack.finishActivityLocked(
r, Activity.RESULT_CANCELED, null, "clear-task-stack", false)) {
--activityNdx;
--numActivities;
@@ -924,8 +964,8 @@
&& (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0
&& !ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) {
if (!ret.finishing) {
- if (stack != null) {
- stack.finishActivityLocked(
+ if (mStack != null) {
+ mStack.finishActivityLocked(
ret, Activity.RESULT_CANCELED, null, "clear-task-top", false);
}
return null;
@@ -939,11 +979,11 @@
return null;
}
- public TaskThumbnail getTaskThumbnailLocked() {
- if (stack != null) {
- final ActivityRecord resumedActivity = stack.mResumedActivity;
+ TaskThumbnail getTaskThumbnailLocked() {
+ if (mStack != null) {
+ final ActivityRecord resumedActivity = mStack.mResumedActivity;
if (resumedActivity != null && resumedActivity.task == this) {
- final Bitmap thumbnail = stack.screenshotActivitiesLocked(resumedActivity);
+ final Bitmap thumbnail = resumedActivity.screenshotActivityLocked();
setLastThumbnailLocked(thumbnail);
}
}
@@ -952,7 +992,7 @@
return taskThumbnail;
}
- public void removeTaskActivitiesLocked() {
+ void removeTaskActivitiesLocked() {
// Just remove the entire task.
performClearTaskAtIndexLocked(0);
}
@@ -1032,20 +1072,16 @@
}
boolean isResizeable() {
- return !isHomeTask() && (mService.mForceResizableActivities
- || ActivityInfo.isResizeableMode(mResizeMode)) && !mTemporarilyUnresizable;
+ return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode))
+ && !mTemporarilyUnresizable;
}
boolean isOnTopLauncher() {
return isHomeTask() && mIsOnTopLauncher;
}
- boolean inCropWindowsResizeMode() {
- return !isResizeable() && mResizeMode == RESIZE_MODE_CROP_WINDOWS;
- }
-
boolean canGoInDockedStack() {
- return isResizeable() || inCropWindowsResizeMode();
+ return isResizeable();
}
/**
@@ -1072,12 +1108,12 @@
// utility activities.
int activityNdx;
final int numActivities = mActivities.size();
- final boolean relinquish = numActivities == 0 ? false :
- (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
+ final boolean relinquish = numActivities != 0 &&
+ (mActivities.get(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
- if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+ if (relinquish && (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
// This will be the top activity for determining taskDescription. Pre-inc to
// overcome initial decrement below.
++activityNdx;
@@ -1132,7 +1168,7 @@
continue;
}
effectiveNdx = activityNdx;
- if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+ if ((r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
break;
}
}
@@ -1200,6 +1236,7 @@
}
out.attribute(null, ATTR_MIN_WIDTH, String.valueOf(mMinWidth));
out.attribute(null, ATTR_MIN_HEIGHT, String.valueOf(mMinHeight));
+ out.attribute(null, ATTR_PERSIST_TASK_VERSION, String.valueOf(PERSIST_TASK_VERSION));
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
@@ -1266,6 +1303,7 @@
Rect bounds = null;
int minWidth = INVALID_MIN_SIZE;
int minHeight = INVALID_MIN_SIZE;
+ int persistTaskVersion = 0;
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
@@ -1327,8 +1365,6 @@
callingPackage = attrValue;
} else if (ATTR_RESIZE_MODE.equals(attrName)) {
resizeMode = Integer.parseInt(attrValue);
- resizeMode = (resizeMode == RESIZE_MODE_CROP_WINDOWS)
- ? RESIZE_MODE_FORCE_RESIZEABLE : resizeMode;
} else if (ATTR_PRIVILEGED.equals(attrName)) {
privileged = Boolean.parseBoolean(attrValue);
} else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
@@ -1337,6 +1373,8 @@
minWidth = Integer.parseInt(attrValue);
} else if (ATTR_MIN_HEIGHT.equals(attrName)) {
minHeight = Integer.parseInt(attrValue);
+ } else if (ATTR_PERSIST_TASK_VERSION.equals(attrName)) {
+ persistTaskVersion = Integer.parseInt(attrValue);
} else {
Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
}
@@ -1391,6 +1429,16 @@
+ ": effectiveUid=" + effectiveUid);
}
+ if (persistTaskVersion < 1) {
+ // We need to convert the resize mode of home activities saved before version one if
+ // they are marked as RESIZE_MODE_RESIZEABLE to RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION
+ // since we didn't have that differentiation before version 1 and the system didn't
+ // resize home activities before then.
+ if (taskType == HOME_ACTIVITY_TYPE && resizeMode == RESIZE_MODE_RESIZEABLE) {
+ resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+ }
+ }
+
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
@@ -1417,7 +1465,7 @@
// If the task has no requested minimal size, we'd like to enforce a minimal size
// so that the user can not render the task too small to manipulate. We don't need
// to do this for the pinned stack as the bounds are controlled by the system.
- if (stack.mStackId != PINNED_STACK_ID) {
+ if (getStackId() != PINNED_STACK_ID) {
if (minWidth == INVALID_MIN_SIZE) {
minWidth = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
}
@@ -1472,16 +1520,17 @@
if (Objects.equals(mBounds, bounds)) {
return false;
}
- final Configuration oldConfig = mOverrideConfig;
+ mTmpConfig.setTo(getOverrideConfiguration());
final boolean oldFullscreen = mFullscreen;
+ final Configuration newConfig = getOverrideConfiguration();
mFullscreen = bounds == null;
if (mFullscreen) {
- if (mBounds != null && StackId.persistTaskBounds(stack.mStackId)) {
+ if (mBounds != null && StackId.persistTaskBounds(mStack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
mBounds = null;
- mOverrideConfig = Configuration.EMPTY;
+ newConfig.unset();
} else {
mTmpRect.set(bounds);
adjustForMinimalTaskDimensions(mTmpRect);
@@ -1490,18 +1539,19 @@
} else {
mBounds.set(mTmpRect);
}
- if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
+ if (mStack == null || StackId.persistTaskBounds(mStack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
- mOverrideConfig = calculateOverrideConfig(mTmpRect, insetBounds,
+ calculateOverrideConfig(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
+ onOverrideConfigurationChanged(newConfig);
if (mFullscreen != oldFullscreen) {
mService.mStackSupervisor.scheduleReportMultiWindowModeChanged(this);
}
- return !mOverrideConfig.equals(oldConfig);
+ return !mTmpConfig.equals(newConfig);
}
private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
@@ -1526,8 +1576,9 @@
inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
}
- private Configuration calculateOverrideConfig(Rect bounds, Rect insetBounds,
- boolean overrideWidth, boolean overrideHeight) {
+ /** Clears passed config and fills it with new override values. */
+ private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
+ boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
subtractNonDecorInsets(
@@ -1537,16 +1588,16 @@
mTmpStableBounds, insetBounds != null ? insetBounds : bounds,
overrideWidth, overrideHeight);
- // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
+ // For calculating screenWidthDp, screenHeightDp, we use the stable inset screen area,
// i.e. the screen area without the system bars.
- final Configuration serviceConfig = mService.mGlobalConfiguration;
- final Configuration config = new Configuration(Configuration.EMPTY);
- // TODO(multidisplay): Update Dp to that of display stack is on.
- final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ // Additionally task dimensions should not be bigger than its parents dimensions.
+ final Configuration parentConfig = getParent().getConfiguration();
+ config.unset();
+ final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
config.screenWidthDp =
- Math.min((int)(mTmpStableBounds.width() / density), serviceConfig.screenWidthDp);
+ Math.min((int)(mTmpStableBounds.width() / density), parentConfig.screenWidthDp);
config.screenHeightDp =
- Math.min((int)(mTmpStableBounds.height() / density), serviceConfig.screenHeightDp);
+ Math.min((int)(mTmpStableBounds.height() / density), parentConfig.screenHeightDp);
// TODO: Orientation?
config.orientation = (config.screenWidthDp <= config.screenHeightDp)
@@ -1558,14 +1609,15 @@
// never go away in Honeycomb.
final int compatScreenWidthDp = (int)(mTmpNonDecorBounds.width() / density);
final int compatScreenHeightDp = (int)(mTmpNonDecorBounds.height() / density);
- final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
+ // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start override
+ // calculation with partial default.
+ final int sl = Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_XLARGE;
final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
- final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);;
+ final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
config.smallestScreenWidthDp = mService.mWindowManager.getSmallestWidthForTaskBounds(
insetBounds != null ? insetBounds : bounds);
- return config;
}
/**
@@ -1574,12 +1626,14 @@
* {@param config}.
*/
Configuration extractOverrideConfig(Configuration config) {
- final Configuration extracted = new Configuration(Configuration.EMPTY);
+ final Configuration extracted = new Configuration();
extracted.screenWidthDp = config.screenWidthDp;
extracted.screenHeightDp = config.screenHeightDp;
extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
extracted.orientation = config.orientation;
- extracted.screenLayout = config.screenLayout;
+ // We're only overriding LONG, SIZE and COMPAT parts of screenLayout.
+ extracted.screenLayout = config.screenLayout & (Configuration.SCREENLAYOUT_LONG_MASK
+ | Configuration.SCREENLAYOUT_SIZE_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED);
return extracted;
}
@@ -1592,29 +1646,6 @@
return bounds;
}
- /**
- * Update fields that are not overridden for task from global configuration.
- *
- * @param globalConfig global configuration to update from.
- */
- void sanitizeOverrideConfiguration(Configuration globalConfig) {
- // If it's fullscreen, the override config should be empty and we should leave it alone.
- if (mFullscreen) {
- return;
- }
-
- // screenLayout field is set in #calculateOverrideConfig but only part of it is really
- // overridden - aspect ratio and size. Other flags (like layout direction) can be updated
- // separately in global config and they also must be updated in override config.
- int overrideScreenLayout = mOverrideConfig.screenLayout;
- int newScreenLayout = globalConfig.screenLayout;
- newScreenLayout = (newScreenLayout & ~SCREENLAYOUT_LONG_MASK)
- | (overrideScreenLayout & SCREENLAYOUT_LONG_MASK);
- newScreenLayout = (newScreenLayout & ~SCREENLAYOUT_SIZE_MASK)
- | (overrideScreenLayout & SCREENLAYOUT_SIZE_MASK);
- mOverrideConfig.screenLayout = newScreenLayout;
- }
-
static Rect validateBounds(Rect bounds) {
if (bounds != null && bounds.isEmpty()) {
Slog.wtf(TAG, "Received strange task bounds: " + bounds, new Throwable());
@@ -1626,7 +1657,7 @@
/** Updates the task's bounds and override configuration to match what is expected for the
* input stack. */
void updateOverrideConfigurationForStack(ActivityStack inStack) {
- if (stack != null && stack == inStack) {
+ if (mStack != null && mStack == inStack) {
return;
}
@@ -1670,17 +1701,17 @@
return null;
}
- if (stack == null) {
+ if (mStack == null) {
return null;
}
- final int stackId = stack.mStackId;
+ final int stackId = mStack.mStackId;
if (stackId == HOME_STACK_ID
|| stackId == FULLSCREEN_WORKSPACE_STACK_ID
|| (stackId == DOCKED_STACK_ID && !isResizeable())) {
- return isResizeable() ? stack.mBounds : null;
+ return isResizeable() ? mStack.mBounds : null;
} else if (!StackId.persistTaskBounds(stackId)) {
- return stack.mBounds;
+ return mStack.mBounds;
}
return mLastNonFullscreenBounds;
}
@@ -1688,7 +1719,7 @@
boolean canMatchRootAffinity() {
// We don't allow root affinity matching on the pinned stack as no other task should
// be launching in it based on affinity.
- return rootAffinity != null && (stack == null || stack.mStackId != PINNED_STACK_ID);
+ return rootAffinity != null && getStackId() != PINNED_STACK_ID;
}
void dump(PrintWriter pw, String prefix) {
@@ -1779,9 +1810,7 @@
if (lastDescription != null) {
pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
}
- if (stack != null) {
- pw.print(prefix); pw.print("stackId="); pw.println(stack.mStackId);
- }
+ pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" isResizeable=" + isResizeable());
@@ -1798,7 +1827,7 @@
sb.append(" U=");
sb.append(userId);
sb.append(" StackId=");
- sb.append(stack != null ? stack.mStackId : INVALID_STACK_ID);
+ sb.append(getStackId());
sb.append(" sz=");
sb.append(mActivities.size());
sb.append('}');
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ba42d3f..9697855 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -115,7 +115,7 @@
// Amount of time we wait for observers to handle a user switch before
// giving up on them and unfreezing the screen.
- static final int USER_SWITCH_TIMEOUT = 2 * 1000;
+ static final int USER_SWITCH_TIMEOUT = 3 * 1000;
private final Object mLock;
private final Injector mInjector;
@@ -1103,6 +1103,7 @@
mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
}
final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
+ final long dispatchStartedTime = SystemClock.elapsedRealtime();
for (int i = 0; i < observerCount; i++) {
try {
// Prepend with unique prefix to guarantee that keys are unique
@@ -1114,6 +1115,11 @@
@Override
public void sendResult(Bundle data) throws RemoteException {
synchronized (mLock) {
+ long delay = SystemClock.elapsedRealtime() - dispatchStartedTime;
+ if (delay > USER_SWITCH_TIMEOUT) {
+ Slog.wtf(TAG, "User switch timeout: observer " + name
+ + " sent result after " + delay + " ms");
+ }
// Early return if this session is no longer valid
if (curWaitingUserSwitchCallbacks
!= mCurWaitingUserSwitchCallbacks) {
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index ff8014c..48238b6 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -87,7 +87,7 @@
state = newState;
}
- private static String stateToString(int state) {
+ static String stateToString(int state) {
switch (state) {
case STATE_BOOTING: return "BOOTING";
case STATE_RUNNING_LOCKED: return "RUNNING_LOCKED";
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 96ba259..0f351f6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -209,6 +209,7 @@
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
private static final int MSG_SET_ALL_VOLUMES = 10;
private static final int MSG_REPORT_NEW_ROUTES = 12;
+ private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
@@ -514,10 +515,6 @@
// Request to override default use of A2DP for media.
private boolean mBluetoothA2dpEnabled;
- // FIXME: remove when MediaRouter does not use setBluetoothA2dpOn() anymore
- // state of bluetooth A2DP enable request sen by deprecated APIs setBluetoothA2dpOn() and
- // isBluettohA2dpOn()
- private boolean mBluetoothA2dpEnabledExternal;
private final Object mBluetoothA2dpEnabledLock = new Object();
// Monitoring of audio routes. Protected by mCurAudioRoutes.
@@ -2718,23 +2715,22 @@
return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
}
- /**
- * Deprecated.
- * Keep stub implementation until MediaRouter stops using it.
- * @deprecated
- * */
+ /** @see AudioManager#setBluetoothA2dpOn(boolean) */
public void setBluetoothA2dpOn(boolean on) {
- mBluetoothA2dpEnabledExternal = on;
- Log.e(TAG, "setBluetoothA2dpOn() is deprecated, now a no-op",
- new Exception("Deprecated use of setBluetoothA2dpOn()"));
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBluetoothA2dpEnabled = on;
+ sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ null, 0);
+ }
}
- /** Deprecated.
- * Keep stub implementation until MediaRouter stops using it
- * @deprecated
- * */
+ /** @see AudioManager#isBluetoothA2dpOn() */
public boolean isBluetoothA2dpOn() {
- return mBluetoothA2dpEnabledExternal;
+ synchronized (mBluetoothA2dpEnabledLock) {
+ return mBluetoothA2dpEnabled;
+ }
}
/** @see AudioManager#startBluetoothSco() */
@@ -3807,11 +3803,6 @@
Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
+ address + ")");
}
- if ((state == 0) && ((type == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
- (type == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
- (type == AudioSystem.DEVICE_OUT_LINE))) {
- setBluetoothA2dpOnInt(true);
- }
int delay = checkSendBecomingNoisyIntent(type, state);
queueMsgUnderWakeLock(mAudioHandler,
MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
@@ -4623,6 +4614,7 @@
break;
case MSG_SET_FORCE_USE:
+ case MSG_SET_FORCE_BT_A2DP_USE:
setForceUse(msg.arg1, msg.arg2);
break;
@@ -5030,7 +5022,6 @@
devices |= dev;
}
}
-
if (devices == device) {
sendMsg(mAudioHandler,
MSG_BROADCAST_AUDIO_BECOMING_NOISY,
@@ -5126,6 +5117,11 @@
}
synchronized (mConnectedDevices) {
+ if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
+ (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (device == AudioSystem.DEVICE_OUT_LINE))) {
+ setBluetoothA2dpOnInt(true);
+ }
boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) ||
(((device & AudioSystem.DEVICE_BIT_IN) != 0) &&
((device & ~AudioSystem.DEVICE_IN_ALL_USB) == 0));
@@ -5609,6 +5605,7 @@
public void setBluetoothA2dpOnInt(boolean on) {
synchronized (mBluetoothA2dpEnabledLock) {
mBluetoothA2dpEnabled = on;
+ mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
}
@@ -5623,6 +5620,8 @@
} else { // config == AudioSystem.FORCE_NONE
mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ALL_A2DP;
}
+ sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
+ SENDMSG_NOOP, 0, 0, null, 0);
break;
case AudioSystem.FOR_DOCK:
if (config == AudioSystem.FORCE_ANALOG_DOCK) {
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 66aa403..5772a57 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -20,7 +20,6 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
-import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ContentProvider;
@@ -28,7 +27,6 @@
import android.content.IOnPrimaryClipChangedListener;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -47,23 +45,57 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.server.SystemService;
+
import java.util.HashSet;
import java.util.List;
/**
* Implementation of the clipboard for copy and paste.
*/
-public class ClipboardService extends IClipboard.Stub {
+public class ClipboardService extends SystemService {
private static final String TAG = "ClipboardService";
- private final Context mContext;
private final IActivityManager mAm;
private final IUserManager mUm;
private final PackageManager mPm;
private final AppOpsManager mAppOps;
private final IBinder mPermissionOwner;
+ private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
+
+ /**
+ * Instantiates the clipboard.
+ */
+ public ClipboardService(Context context) {
+ super(context);
+
+ mAm = ActivityManagerNative.getDefault();
+ mPm = getContext().getPackageManager();
+ mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
+ mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ IBinder permOwner = null;
+ try {
+ permOwner = mAm.newUriPermissionOwner("clipboard");
+ } catch (RemoteException e) {
+ Slog.w("clipboard", "AM dead", e);
+ }
+ mPermissionOwner = permOwner;
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
+ }
+
+ @Override
+ public void onCleanupUser(int userId) {
+ synchronized (mClipboards) {
+ mClipboards.remove(userId);
+ }
+ }
+
private class ListenerInfo {
final int mUid;
final String mPackageName;
@@ -89,52 +121,141 @@
}
}
- private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
+ private class ClipboardImpl extends IClipboard.Stub {
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ if (!(e instanceof SecurityException)) {
+ Slog.wtf("clipboard", "Exception: ", e);
+ }
+ throw e;
+ }
- /**
- * Instantiates the clipboard.
- */
- public ClipboardService(Context context) {
- mContext = context;
- mAm = ActivityManagerNative.getDefault();
- mPm = context.getPackageManager();
- mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
- mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
- IBinder permOwner = null;
- try {
- permOwner = mAm.newUriPermissionOwner("clipboard");
- } catch (RemoteException e) {
- Slog.w("clipboard", "AM dead", e);
}
- mPermissionOwner = permOwner;
- // Remove the clipboard if a user is removed
- IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_USER_REMOVED.equals(action)) {
- removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ @Override
+ public void setPrimaryClip(ClipData clip, String callingPackage) {
+ synchronized (this) {
+ if (clip != null && clip.getItemCount() <= 0) {
+ throw new IllegalArgumentException("No items");
+ }
+ final int callingUid = Binder.getCallingUid();
+ if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+ checkDataOwnerLocked(clip, callingUid);
+ final int userId = UserHandle.getUserId(callingUid);
+ PerUserClipboard clipboard = getClipboard(userId);
+ revokeUris(clipboard);
+ setPrimaryClipInternal(clipboard, clip);
+ List<UserInfo> related = getRelatedProfiles(userId);
+ if (related != null) {
+ int size = related.size();
+ if (size > 1) { // Related profiles list include the current profile.
+ boolean canCopy = false;
+ try {
+ canCopy = !mUm.getUserRestrictions(userId).getBoolean(
+ UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception calling UserManager: " + e);
+ }
+ // Copy clip data to related users if allowed. If disallowed, then remove
+ // primary clip in related users to prevent pasting stale content.
+ if (!canCopy) {
+ clip = null;
+ } else {
+ // We want to fix the uris of the related user's clip without changing the
+ // uris of the current user's clip.
+ // So, copy the ClipData, and then copy all the items, so that nothing
+ // is shared in memmory.
+ clip = new ClipData(clip);
+ for (int i = clip.getItemCount() - 1; i >= 0; i--) {
+ clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
+ }
+ clip.fixUrisLight(userId);
+ }
+ for (int i = 0; i < size; i++) {
+ int id = related.get(i).id;
+ if (id != userId) {
+ setPrimaryClipInternal(getClipboard(id), clip);
+ }
+ }
+ }
}
}
- }, userFilter);
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- try {
- return super.onTransact(code, data, reply, flags);
- } catch (RuntimeException e) {
- if (!(e instanceof SecurityException)) {
- Slog.wtf("clipboard", "Exception: ", e);
- }
- throw e;
}
-
- }
+
+ @Override
+ public ClipData getPrimaryClip(String pkg) {
+ synchronized (this) {
+ if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ pkg) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ addActiveOwnerLocked(Binder.getCallingUid(), pkg);
+ return getClipboard().primaryClip;
+ }
+ }
+
+ @Override
+ public ClipDescription getPrimaryClipDescription(String callingPackage) {
+ synchronized (this) {
+ if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ PerUserClipboard clipboard = getClipboard();
+ return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
+ }
+ }
+
+ @Override
+ public boolean hasPrimaryClip(String callingPackage) {
+ synchronized (this) {
+ if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ return getClipboard().primaryClip != null;
+ }
+ }
+
+ @Override
+ public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+ String callingPackage) {
+ synchronized (this) {
+ getClipboard().primaryClipListeners.register(listener,
+ new ListenerInfo(Binder.getCallingUid(), callingPackage));
+ }
+ }
+
+ @Override
+ public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+ synchronized (this) {
+ getClipboard().primaryClipListeners.unregister(listener);
+ }
+ }
+
+ @Override
+ public boolean hasClipboardText(String callingPackage) {
+ synchronized (this) {
+ if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ PerUserClipboard clipboard = getClipboard();
+ if (clipboard.primaryClip != null) {
+ CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
+ return text != null && text.length() > 0;
+ }
+ return false;
+ }
+ }
+ };
private PerUserClipboard getClipboard() {
return getClipboard(UserHandle.getCallingUserId());
@@ -151,64 +272,6 @@
}
}
- private void removeClipboard(int userId) {
- synchronized (mClipboards) {
- mClipboards.remove(userId);
- }
- }
-
- public void setPrimaryClip(ClipData clip, String callingPackage) {
- synchronized (this) {
- if (clip != null && clip.getItemCount() <= 0) {
- throw new IllegalArgumentException("No items");
- }
- final int callingUid = Binder.getCallingUid();
- if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return;
- }
- checkDataOwnerLocked(clip, callingUid);
- final int userId = UserHandle.getUserId(callingUid);
- PerUserClipboard clipboard = getClipboard(userId);
- revokeUris(clipboard);
- setPrimaryClipInternal(clipboard, clip);
- List<UserInfo> related = getRelatedProfiles(userId);
- if (related != null) {
- int size = related.size();
- if (size > 1) { // Related profiles list include the current profile.
- boolean canCopy = false;
- try {
- canCopy = !mUm.getUserRestrictions(userId).getBoolean(
- UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote Exception calling UserManager: " + e);
- }
- // Copy clip data to related users if allowed. If disallowed, then remove
- // primary clip in related users to prevent pasting stale content.
- if (!canCopy) {
- clip = null;
- } else {
- // We want to fix the uris of the related user's clip without changing the
- // uris of the current user's clip.
- // So, copy the ClipData, and then copy all the items, so that nothing
- // is shared in memmory.
- clip = new ClipData(clip);
- for (int i = clip.getItemCount() - 1; i >= 0; i--) {
- clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
- }
- clip.fixUrisLight(userId);
- }
- for (int i = 0; i < size; i++) {
- int id = related.get(i).id;
- if (id != userId) {
- setPrimaryClipInternal(getClipboard(id), clip);
- }
- }
- }
- }
- }
- }
-
List<UserInfo> getRelatedProfiles(int userId) {
final List<UserInfo> related;
final long origId = Binder.clearCallingIdentity();
@@ -251,67 +314,6 @@
Binder.restoreCallingIdentity(ident);
}
}
-
- public ClipData getPrimaryClip(String pkg) {
- synchronized (this) {
- if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- pkg) != AppOpsManager.MODE_ALLOWED) {
- return null;
- }
- addActiveOwnerLocked(Binder.getCallingUid(), pkg);
- return getClipboard().primaryClip;
- }
- }
-
- public ClipDescription getPrimaryClipDescription(String callingPackage) {
- synchronized (this) {
- if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return null;
- }
- PerUserClipboard clipboard = getClipboard();
- return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
- }
- }
-
- public boolean hasPrimaryClip(String callingPackage) {
- synchronized (this) {
- if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
- return getClipboard().primaryClip != null;
- }
- }
-
- public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
- String callingPackage) {
- synchronized (this) {
- getClipboard().primaryClipListeners.register(listener,
- new ListenerInfo(Binder.getCallingUid(), callingPackage));
- }
- }
-
- public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
- synchronized (this) {
- getClipboard().primaryClipListeners.unregister(listener);
- }
- }
-
- public boolean hasClipboardText(String callingPackage) {
- synchronized (this) {
- if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
- PerUserClipboard clipboard = getClipboard();
- if (clipboard.primaryClip != null) {
- CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
- return text != null && text.length() > 0;
- }
- return false;
- }
- }
private final void checkUriOwnerLocked(Uri uri, int uid) {
if (!"content".equals(uri.getScheme())) {
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 28e724c..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;
@@ -186,6 +221,7 @@
static final String CMD_FLUSH = "flush";
static final String CMD_LIST = "list";
static final String CMD_STATS = "stats";
+ static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
static final String CMD_DEFAULT = CMD_STATS;
@Override
@@ -203,6 +239,8 @@
case CMD_FLUSH:
cmdFlush(fd, pw, args);
return;
+ case CMD_DUMPSYS:
+ // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
case CMD_LIST:
cmdList(fd, pw, args);
return;
@@ -226,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/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
similarity index 70%
rename from core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
rename to services/core/java/com/android/server/connectivity/MockableSystemProperties.java
index fb76e67..4f68652 100644
--- a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
+++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-package android.print.mockservice;
+package com.android.server.connectivity;
-import android.app.Activity;
-import android.os.Bundle;
+import android.os.SystemProperties;
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+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/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index e7198d3..363d7cb 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -134,7 +134,9 @@
@Override
// Called concurrently by multiple binder threads.
- public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs) {
+ // This method must not block or perform long-running operations.
+ public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
+ String hostname, String[] ipAddresses, int ipAddressesCount, int uid) {
maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)",
netId, eventType, returnCode, latencyMs));
@@ -146,6 +148,14 @@
batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
}
+ @Override
+ // Called concurrently by multiple binder threads.
+ // This method must not block or perform long-running operations.
+ public synchronized void onConnectEvent(int netId, int latencyMs, String ipAddr, int port,
+ int uid) {
+ maybeVerboseLog(String.format("onConnectEvent(%d, %d)", netId, latencyMs));
+ }
+
public synchronized void dump(PrintWriter writer) {
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println(TAG + ":");
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 42d80fc..c73d1dd 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -23,7 +23,6 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -37,14 +36,12 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.net.util.Stopwatch;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.util.Stopwatch;
import android.os.Handler;
import android.os.Message;
-import android.os.Process;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CellIdentityCdma;
@@ -66,27 +63,39 @@
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
-import java.net.UnknownHostException;
import java.net.URL;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
+import java.net.UnknownHostException;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* {@hide}
*/
public class NetworkMonitor extends StateMachine {
- private static final boolean DBG = false;
private static final String TAG = NetworkMonitor.class.getSimpleName();
- private static final String DEFAULT_SERVER = "connectivitycheck.gstatic.com";
+ private static final boolean DBG = false;
+
+ // Default configuration values for captive portal detection probes.
+ // TODO: append a random length parameter to the default HTTPS url.
+ // TODO: randomize browser version ids in the default User-Agent String.
+ private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
+ private static final String DEFAULT_HTTP_URL =
+ "http://connectivitycheck.gstatic.com/generate_204";
+ private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204";
+ private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) "
+ + "AppleWebKit/537.36 (KHTML, like Gecko) "
+ + "Chrome/52.0.2743.82 Safari/537.36";
+
private static final int SOCKET_TIMEOUT_MS = 10000;
+ private static final int PROBE_TIMEOUT_MS = 3000;
+
public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
"android.net.conn.NETWORK_CONDITIONS_MEASURED";
public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
@@ -202,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.
@@ -224,6 +235,9 @@
private final Stopwatch mEvaluationTimer = new Stopwatch();
+ // This variable is set before transitioning to the mCaptivePortalState.
+ private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
+
public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
NetworkRequest defaultRequest) {
this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog());
@@ -253,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;
@@ -389,6 +404,8 @@
sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
}
}));
+ intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL,
+ mLastPortalProbeResult.detectUrl);
intent.setFlags(
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
@@ -412,14 +429,22 @@
*/
@VisibleForTesting
public static final class CaptivePortalProbeResult {
- static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(599, null);
+ static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(599);
- final int mHttpResponseCode; // HTTP response code returned from Internet probe.
- final String mRedirectUrl; // Redirect destination returned from Internet probe.
+ private final int mHttpResponseCode; // HTTP response code returned from Internet probe.
+ final String redirectUrl; // Redirect destination returned from Internet probe.
+ final String detectUrl; // URL where a 204 response code indicates
+ // captive portal has been appeased.
- public CaptivePortalProbeResult(int httpResponseCode, String redirectUrl) {
+ public CaptivePortalProbeResult(
+ int httpResponseCode, String redirectUrl, String detectUrl) {
mHttpResponseCode = httpResponseCode;
- mRedirectUrl = redirectUrl;
+ this.redirectUrl = redirectUrl;
+ this.detectUrl = detectUrl;
+ }
+
+ public CaptivePortalProbeResult(int httpResponseCode) {
+ this(httpResponseCode, null, null);
}
boolean isSuccessful() { return mHttpResponseCode == 204; }
@@ -492,7 +517,8 @@
transitionTo(mValidatedState);
} else if (probeResult.isPortal()) {
mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
- NETWORK_TEST_RESULT_INVALID, mNetId, probeResult.mRedirectUrl));
+ NETWORK_TEST_RESULT_INVALID, mNetId, probeResult.redirectUrl));
+ mLastPortalProbeResult = probeResult;
transitionTo(mCaptivePortalState);
} else {
final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
@@ -500,7 +526,7 @@
logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
mConnectivityServiceHandler.sendMessage(obtainMessage(
EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId,
- probeResult.mRedirectUrl));
+ probeResult.redirectUrl));
if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
// Don't continue to blame UID forever.
TrafficStats.clearThreadStatsUid();
@@ -585,22 +611,36 @@
}
}
- private static String getCaptivePortalServerUrl(Context context, boolean isHttps) {
- String server = Settings.Global.getString(context.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_SERVER);
- if (server == null) server = DEFAULT_SERVER;
- return (isHttps ? "https" : "http") + "://" + server + "/generate_204";
+ private static String getCaptivePortalServerHttpsUrl(Context context) {
+ return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
}
- public static String getCaptivePortalServerUrl(Context context) {
- return getCaptivePortalServerUrl(context, false);
+ public static String getCaptivePortalServerHttpUrl(Context context) {
+ return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
+ }
+
+ private static String getCaptivePortalFallbackUrl(Context context) {
+ return getSetting(context,
+ Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
+ }
+
+ private static String getCaptivePortalUserAgent(Context context) {
+ return getSetting(context, Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
+ }
+
+ private static String getSetting(Context context, String symbol, String defaultValue) {
+ final String value = Settings.Global.getString(context.getContentResolver(), symbol);
+ return value != null ? value : defaultValue;
}
@VisibleForTesting
protected CaptivePortalProbeResult isCaptivePortal() {
- if (!mIsCaptivePortalCheckEnabled) return new CaptivePortalProbeResult(204, null);
+ if (!mIsCaptivePortalCheckEnabled) {
+ validationLog("Validation disabled.");
+ return new CaptivePortalProbeResult(204);
+ }
- URL pacUrl = null, httpUrl = null, httpsUrl = null;
+ URL pacUrl = null, httpsUrl = null, httpUrl = null, fallbackUrl = null;
// On networks with a PAC instead of fetching a URL that should result in a 204
// response, we instead simply fetch the PAC script. This is done for a few reasons:
@@ -621,20 +661,17 @@
// results for network validation.
final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
- try {
- pacUrl = new URL(proxyInfo.getPacFileUrl().toString());
- } catch (MalformedURLException e) {
- validationLog("Invalid PAC URL: " + proxyInfo.getPacFileUrl().toString());
+ pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
+ if (pacUrl == null) {
return CaptivePortalProbeResult.FAILED;
}
}
if (pacUrl == null) {
- try {
- httpUrl = new URL(getCaptivePortalServerUrl(mContext, false));
- httpsUrl = new URL(getCaptivePortalServerUrl(mContext, true));
- } catch (MalformedURLException e) {
- validationLog("Bad validation URL: " + getCaptivePortalServerUrl(mContext, false));
+ httpsUrl = makeURL(getCaptivePortalServerHttpsUrl(mContext));
+ httpUrl = makeURL(getCaptivePortalServerHttpUrl(mContext));
+ fallbackUrl = makeURL(getCaptivePortalFallbackUrl(mContext));
+ if (httpUrl == null || httpsUrl == null) {
return CaptivePortalProbeResult.FAILED;
}
}
@@ -680,7 +717,7 @@
if (pacUrl != null) {
result = sendHttpProbe(pacUrl, ValidationProbeEvent.PROBE_PAC);
} else if (mUseHttps) {
- result = sendParallelHttpProbes(httpsUrl, httpUrl);
+ result = sendParallelHttpProbes(httpsUrl, httpUrl, fallbackUrl);
} else {
result = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP);
}
@@ -710,6 +747,10 @@
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
+ final String userAgent = getCaptivePortalUserAgent(mContext);
+ if (userAgent != null) {
+ urlConnection.setRequestProperty("User-Agent", userAgent);
+ }
// Time how long it takes to get a response to our request
long requestTimestamp = SystemClock.elapsedRealtime();
@@ -755,28 +796,24 @@
}
}
logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
- return new CaptivePortalProbeResult(httpResponseCode, redirectUrl);
+ return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
}
- private CaptivePortalProbeResult sendParallelHttpProbes(URL httpsUrl, URL httpUrl) {
- // Number of probes to wait for. We might wait for all of them, but we might also return if
- // only one of them has replied. For example, we immediately return if the HTTP probe finds
- // a captive portal, even if the HTTPS probe is timing out.
+ private CaptivePortalProbeResult sendParallelHttpProbes(
+ URL httpsUrl, URL httpUrl, URL fallbackUrl) {
+ // Number of probes to wait for. If a probe completes with a conclusive answer
+ // it shortcuts the latch immediately by forcing the count to 0.
final CountDownLatch latch = new CountDownLatch(2);
- // Which probe result we're going to use. This doesn't need to be atomic, but it does need
- // to be final because otherwise we can't set it from the ProbeThreads.
- final AtomicReference<CaptivePortalProbeResult> finalResult = new AtomicReference<>();
-
final class ProbeThread extends Thread {
private final boolean mIsHttps;
- private volatile CaptivePortalProbeResult mResult;
+ private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
public ProbeThread(boolean isHttps) {
mIsHttps = isHttps;
}
- public CaptivePortalProbeResult getResult() {
+ public CaptivePortalProbeResult result() {
return mResult;
}
@@ -788,32 +825,66 @@
mResult = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP);
}
if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
- // HTTPS succeeded, or HTTP found a portal. Don't wait for the other probe.
- finalResult.compareAndSet(null, mResult);
- latch.countDown();
+ // Stop waiting immediately if https succeeds or if http finds a portal.
+ while (latch.getCount() > 0) {
+ latch.countDown();
+ }
}
- // Signal that one probe has completed. If we've already made a decision, or if this
- // is the second probe, the latch will be at zero and we'll return a result.
+ // Signal this probe has completed.
latch.countDown();
}
}
- ProbeThread httpsProbe = new ProbeThread(true);
- ProbeThread httpProbe = new ProbeThread(false);
- httpsProbe.start();
- httpProbe.start();
+ final ProbeThread httpsProbe = new ProbeThread(true);
+ final ProbeThread httpProbe = new ProbeThread(false);
try {
- latch.await();
+ httpsProbe.start();
+ httpProbe.start();
+ latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
- validationLog("Error: probe wait interrupted!");
+ validationLog("Error: probes wait interrupted!");
return CaptivePortalProbeResult.FAILED;
}
- // If there was no deciding probe, that means that both probes completed. Return HTTPS.
- finalResult.compareAndSet(null, httpsProbe.getResult());
+ final CaptivePortalProbeResult httpsResult = httpsProbe.result();
+ final CaptivePortalProbeResult httpResult = httpProbe.result();
- return finalResult.get();
+ // Look for a conclusive probe result first.
+ if (httpResult.isPortal()) {
+ return httpResult;
+ }
+ // httpsResult.isPortal() is not expected, but check it nonetheless.
+ if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
+ return httpsResult;
+ }
+ // If a fallback url is specified, use a fallback probe to try again portal detection.
+ if (fallbackUrl != null) {
+ CaptivePortalProbeResult result =
+ sendHttpProbe(fallbackUrl, ValidationProbeEvent.PROBE_FALLBACK);
+ if (result.isPortal()) {
+ return result;
+ }
+ }
+ // Otherwise wait until https probe completes and use its result.
+ try {
+ httpsProbe.join();
+ } catch (InterruptedException e) {
+ validationLog("Error: https probe wait interrupted!");
+ return CaptivePortalProbeResult.FAILED;
+ }
+ return httpsProbe.result();
+ }
+
+ private URL makeURL(String url) {
+ if (url != null) {
+ try {
+ return new URL(url);
+ } catch (MalformedURLException e) {
+ validationLog("Bad URL: " + url);
+ }
+ }
+ return null;
}
/**
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 50faf3b..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;
}
@@ -405,11 +409,13 @@
// Check carrier config for entitlement checks
final CarrierConfigManager configManager = (CarrierConfigManager) mContext
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
- CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
-
- if (!isEntitlementCheckRequired) {
- return false;
+ if (configManager != null && configManager.getConfig() != null) {
+ // we do have a CarrierConfigManager and it has a config.
+ boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
+ CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+ if (!isEntitlementCheckRequired) {
+ return false;
+ }
}
return (provisionApp.length == 2);
}
@@ -895,7 +901,7 @@
}
} else {
mUsbTetherRequested = true;
- usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS);
+ usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false);
}
} else {
final long ident = Binder.clearCallingIdentity();
@@ -905,7 +911,7 @@
Binder.restoreCallingIdentity(ident);
}
if (mRndisEnabled) {
- usbManager.setCurrentFunction(null);
+ usbManager.setCurrentFunction(null, false);
}
mUsbTetherRequested = false;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ede3bda..afc6247 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -27,6 +27,8 @@
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -76,6 +78,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.LegacyVpnInfo;
@@ -241,12 +244,14 @@
/**
* Update current state, dispaching event to listeners.
*/
- private void updateState(DetailedState detailedState, String reason) {
+ @VisibleForTesting
+ protected void updateState(DetailedState detailedState, String reason) {
if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
mNetworkInfo.setDetailedState(detailedState, reason, null);
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
+ updateAlwaysOnNotification(detailedState);
}
/**
@@ -280,7 +285,10 @@
}
mLockdown = (mAlwaysOn && lockdown);
- if (!isCurrentPreparedPackage(packageName)) {
+ if (isCurrentPreparedPackage(packageName)) {
+ updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
+ } else {
+ // Prepare this app. The notification will update as a side-effect of updateState().
prepareInternal(packageName);
}
maybeRegisterPackageChangeReceiverLocked(packageName);
@@ -682,22 +690,19 @@
}
}
- private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) {
- networkInfo.setIsAvailable(false);
- networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+ private void agentDisconnect(NetworkAgent networkAgent) {
if (networkAgent != null) {
+ NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
+ networkInfo.setIsAvailable(false);
+ networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
networkAgent.sendNetworkInfo(networkInfo);
}
}
- private void agentDisconnect(NetworkAgent networkAgent) {
- NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
- agentDisconnect(networkInfo, networkAgent);
- }
-
private void agentDisconnect() {
if (mNetworkInfo.isConnected()) {
- agentDisconnect(mNetworkInfo, mNetworkAgent);
+ mNetworkInfo.setIsAvailable(false);
+ updateState(DetailedState.DISCONNECTED, "agentDisconnect");
mNetworkAgent = null;
}
}
@@ -1250,6 +1255,43 @@
}
}
+ private void updateAlwaysOnNotification(DetailedState networkState) {
+ final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);
+ updateAlwaysOnNotificationInternal(visible);
+ }
+
+ @VisibleForTesting
+ protected void updateAlwaysOnNotificationInternal(boolean visible) {
+ final UserHandle user = UserHandle.of(mUserHandle);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final NotificationManager notificationManager = NotificationManager.from(mContext);
+ if (!visible) {
+ notificationManager.cancelAsUser(TAG, 0, user);
+ return;
+ }
+ final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ final PendingIntent configIntent = PendingIntent.getActivityAsUser(
+ mContext, /* request */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ null, user);
+ final Notification.Builder builder = new Notification.Builder(mContext)
+ .setDefaults(0)
+ .setSmallIcon(R.drawable.vpn_connected)
+ .setContentTitle(mContext.getString(R.string.vpn_lockdown_disconnected))
+ .setContentText(mContext.getString(R.string.vpn_lockdown_config))
+ .setContentIntent(configIntent)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setOngoing(true)
+ .setColor(mContext.getColor(R.color.system_notification_accent_color));
+ notificationManager.notifyAsUser(TAG, 0, builder.build(), user);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private native int jniCreate(int mtu);
private native String jniGetName(int tun);
private native int jniSetAddresses(String interfaze, String addresses);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 4e236d1..0727629 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -838,7 +838,7 @@
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
return syncManager.computeSyncable(
- account, userId, providerName);
+ account, userId, providerName, false);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -854,6 +854,8 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
+ syncable = normalizeSyncable(syncable);
+
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -1156,6 +1158,15 @@
}
}
+ private static int normalizeSyncable(int syncable) {
+ if (syncable > 0) {
+ return SyncStorageEngine.AuthorityInfo.SYNCABLE;
+ } else if (syncable == 0) {
+ return SyncStorageEngine.AuthorityInfo.NOT_SYNCABLE;
+ }
+ return SyncStorageEngine.AuthorityInfo.UNDEFINED;
+ }
+
/**
* Hide this class since it is not part of api,
* but current unittest framework requires it to be public
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 2d6bef4..03d95b2 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -795,7 +795,17 @@
* Use {@link AuthorityInfo#UNDEFINED} to sync all authorities.
*/
public void scheduleSync(Account requestedAccount, int userId, int reason,
- String requestedAuthority, Bundle extras, int targetSyncState) {
+ String requestedAuthority, Bundle extras, int targetSyncState) {
+ scheduleSync(requestedAccount, userId, reason, requestedAuthority, extras, targetSyncState,
+ 0 /* min delay */);
+ }
+
+ /**
+ * @param minDelayMillis The sync can't land before this delay expires.
+ */
+ private void scheduleSync(Account requestedAccount, int userId, int reason,
+ String requestedAuthority, Bundle extras, int targetSyncState,
+ final long minDelayMillis) {
final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
if (extras == null) {
extras = new Bundle();
@@ -906,7 +916,7 @@
if (result != null
&& result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
scheduleSync(account.account, userId, reason, authority,
- finalExtras, targetSyncState);
+ finalExtras, targetSyncState, minDelayMillis);
}
}
));
@@ -967,7 +977,8 @@
postScheduleSyncMessage(
new SyncOperation(account.account, account.userId,
owningUid, owningPackage, reason, source,
- authority, newExtras, allowParallelSyncs)
+ authority, newExtras, allowParallelSyncs),
+ minDelayMillis
);
} else if (targetSyncState == AuthorityInfo.UNDEFINED
|| targetSyncState == isSyncable) {
@@ -982,14 +993,20 @@
postScheduleSyncMessage(
new SyncOperation(account.account, account.userId,
owningUid, owningPackage, reason, source,
- authority, extras, allowParallelSyncs)
+ authority, extras, allowParallelSyncs),
+ minDelayMillis
);
}
}
}
}
- public int computeSyncable(Account account, int userId, String authority) {
+ private int computeSyncable(Account account, int userId, String authority) {
+ return computeSyncable(account, userId, authority, true);
+ }
+
+ public int computeSyncable(Account account, int userId, String authority,
+ boolean checkAccountAccess) {
final int status = getIsSyncable(account, userId, authority);
if (status == AuthorityInfo.NOT_SYNCABLE) {
return AuthorityInfo.NOT_SYNCABLE;
@@ -1013,7 +1030,7 @@
} catch (RemoteException e) {
/* ignore - local call */
}
- if (!canAccessAccount(account, owningPackage, owningUid)) {
+ if (checkAccountAccess && !canAccessAccount(account, owningPackage, owningUid)) {
Log.w(TAG, "Access to " + account + " denied for package "
+ owningPackage + " in UID " + syncAdapterInfo.uid);
return AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS;
@@ -1088,14 +1105,14 @@
}
/**
- * Schedule sync based on local changes to a provider. Occurs within interval
- * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY].
+ * Schedule sync based on local changes to a provider. We wait for at least LOCAL_SYNC_DELAY
+ * ms to batch syncs.
*/
public void scheduleLocalSync(Account account, int userId, int reason, String authority) {
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
scheduleSync(account, userId, reason, authority, extras,
- AuthorityInfo.UNDEFINED);
+ AuthorityInfo.UNDEFINED, LOCAL_SYNC_DELAY);
}
public SyncAdapterType[] getSyncAdapterTypes(int userId) {
@@ -1152,9 +1169,10 @@
mSyncHandler.sendMessageDelayed(monitorMessage, SYNC_MONITOR_WINDOW_LENGTH_MILLIS);
}
- private void postScheduleSyncMessage(SyncOperation syncOperation) {
- mSyncHandler.obtainMessage(mSyncHandler.MESSAGE_SCHEDULE_SYNC, syncOperation)
- .sendToTarget();
+ private void postScheduleSyncMessage(SyncOperation syncOperation, long minDelayMillis) {
+ ScheduleSyncMessagePayload payload =
+ new ScheduleSyncMessagePayload(syncOperation, minDelayMillis);
+ mSyncHandler.obtainMessage(mSyncHandler.MESSAGE_SCHEDULE_SYNC, payload).sendToTarget();
}
/**
@@ -1194,6 +1212,16 @@
}
}
+ private static class ScheduleSyncMessagePayload {
+ final SyncOperation syncOperation;
+ final long minDelayMillis;
+
+ ScheduleSyncMessagePayload(SyncOperation syncOperation, long minDelayMillis) {
+ this.syncOperation = syncOperation;
+ this.minDelayMillis = minDelayMillis;
+ }
+ }
+
private void clearBackoffSetting(EndPoint target) {
Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(target);
if (backoff != null && backoff.first == SyncStorageEngine.NOT_IN_BACKOFF_MODE &&
@@ -1262,7 +1290,7 @@
if (!op.isPeriodic && op.target.matchesSpec(target)) {
count++;
getJobScheduler().cancel(op.jobId);
- postScheduleSyncMessage(op);
+ postScheduleSyncMessage(op, 0 /* min delay */);
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2417,8 +2445,10 @@
mDataConnectionIsConnected = readDataConnectionState();
switch (msg.what) {
case MESSAGE_SCHEDULE_SYNC:
- SyncOperation op = (SyncOperation) msg.obj;
- scheduleSyncOperationH(op);
+ ScheduleSyncMessagePayload syncPayload =
+ (ScheduleSyncMessagePayload) msg.obj;
+ SyncOperation op = syncPayload.syncOperation;
+ scheduleSyncOperationH(op, syncPayload.minDelayMillis);
break;
case MESSAGE_START_SYNC:
@@ -3101,7 +3131,8 @@
maybeRescheduleSync(syncResult, syncOperation);
} else {
// create a normal sync instance that will respect adapter backoffs
- postScheduleSyncMessage(syncOperation.createOneTimeSyncOperation());
+ postScheduleSyncMessage(syncOperation.createOneTimeSyncOperation(),
+ 0 /* min delay */);
}
historyMessage = ContentResolver.syncErrorToString(
syncResultToErrorNumber(syncResult));
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/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 20bccf1..1991c00 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.BIND_DREAM_SERVICE;
+import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -88,6 +89,8 @@
private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+ private AmbientDisplayConfiguration mDozeConfig;
+
public DreamManagerService(Context context) {
super(context);
mContext = context;
@@ -97,6 +100,7 @@
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
+ mDozeConfig = new AmbientDisplayConfiguration(mContext);
}
@Override
@@ -121,7 +125,7 @@
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.DOZE_ENABLED), false,
+ Settings.Secure.getUriFor(Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP), false,
mDozeEnabledObserver, UserHandle.USER_ALL);
writePulseGestureEnabled();
}
@@ -326,19 +330,12 @@
}
private ComponentName getDozeComponent(int userId) {
- // Read the component from a system property to facilitate debugging.
- // Note that for production devices, the dream should actually be declared in
- // a config.xml resource.
- String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null;
- if (TextUtils.isEmpty(name)) {
- // Read the component from a config.xml resource.
- // The value should be specified in a resource overlay for the product.
- name = mContext.getResources().getString(
- com.android.internal.R.string.config_dozeComponent);
+ if (mDozeConfig.enabled(userId)) {
+ return ComponentName.unflattenFromString(mDozeConfig.ambientDisplayComponent());
+ } else {
+ return null;
}
- boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.DOZE_ENABLED, 1, userId) != 0;
- return TextUtils.isEmpty(name) || !enabled ? null : ComponentName.unflattenFromString(name);
+
}
private ServiceInfo getServiceInfo(ComponentName name) {
diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
index cca9f10..353f450 100644
--- a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
+++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
@@ -99,6 +99,7 @@
};
private boolean mSimNeedsEmergencyAffordance;
private boolean mNetworkNeedsEmergencyAffordance;
+ private boolean mVoiceCapable;
private void requestCellScan() {
mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
@@ -125,8 +126,8 @@
private void updateEmergencyAffordanceNeeded() {
synchronized (mLock) {
- mEmergencyAffordanceNeeded = mSimNeedsEmergencyAffordance ||
- mNetworkNeedsEmergencyAffordance;
+ mEmergencyAffordanceNeeded = mVoiceCapable && (mSimNeedsEmergencyAffordance ||
+ mNetworkNeedsEmergencyAffordance);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
mEmergencyAffordanceNeeded ? 1 : 0);
@@ -157,6 +158,11 @@
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mVoiceCapable = mTelephonyManager.isVoiceCapable();
+ if (!mVoiceCapable) {
+ updateEmergencyAffordanceNeeded();
+ return;
+ }
mSubscriptionManager = SubscriptionManager.from(mContext);
HandlerThread thread = new HandlerThread(TAG);
thread.start();
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index 87da866..5297589 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
@@ -115,6 +115,7 @@
final int result = daemon.authenticate(mOpId, getGroupId());
if (result != 0) {
Slog.w(TAG, "startAuthentication failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "fingeprintd_auth_start_error", result);
onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
return result;
}
diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java
index 6a533c9..640a46f 100644
--- a/services/core/java/com/android/server/fingerprint/EnrollClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnrollClient.java
@@ -88,6 +88,7 @@
final int result = daemon.enroll(mCryptoToken, getGroupId(), timeout);
if (result != 0) {
Slog.w(TAG, "startEnroll failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "fingerprintd_enroll_start_error", result);
onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
return result;
}
diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
index 52dbd5d..26b1916 100644
--- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
@@ -23,6 +23,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
/**
* A class to keep track of the enumeration state for a given client.
@@ -43,6 +44,7 @@
if (result != 0) {
Slog.w(TAG, "start enumerate for user " + getTargetUserId()
+ " failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "fingerprintd_enum_start_error", result);
onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
return result;
}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 5addffb..cda063d 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -194,7 +194,9 @@
@Override
public void binderDied() {
Slog.v(TAG, "fingerprintd died");
+ MetricsLogger.count(mContext, "fingerprintd_died", 1);
mDaemon = null;
+ mCurrentUserId = UserHandle.USER_CURRENT;
handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
}
@@ -210,6 +212,7 @@
updateActiveGroup(ActivityManager.getCurrentUser(), null);
} else {
Slog.w(TAG, "Failed to open Fingerprint HAL!");
+ MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
mDaemon = null;
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/fingerprint/RemovalClient.java b/services/core/java/com/android/server/fingerprint/RemovalClient.java
index bcf2264..f939f41 100644
--- a/services/core/java/com/android/server/fingerprint/RemovalClient.java
+++ b/services/core/java/com/android/server/fingerprint/RemovalClient.java
@@ -24,6 +24,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
/**
* A class to keep track of the remove state for a given client.
@@ -46,6 +47,7 @@
final int result = daemon.remove(mFingerId, getGroupId());
if (result != 0) {
Slog.w(TAG, "startRemove with id = " + mFingerId + " failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "fingerprintd_remove_start_error", result);
onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
return result;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 5dc9d02..72ee218 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2011,6 +2011,9 @@
@ServiceThreadOnly
void standby() {
assertRunOnServiceThread();
+ if (!canGoToStandby()) {
+ return;
+ }
mStandbyMessageReceived = true;
mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
// PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
@@ -2038,10 +2041,13 @@
@ServiceThreadOnly
private void onStandby(final int standbyAction) {
assertRunOnServiceThread();
- if (!canGoToStandby()) return;
mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
invokeVendorCommandListenersOnControlStateChanged(false,
HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
+ if (!canGoToStandby()) {
+ mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+ return;
+ }
final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
disableDevices(new PendingActionClearedCallback() {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 74095ac..87f4030 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Build;
import android.os.LocaleList;
+import android.os.ShellCallback;
import android.util.Log;
import android.view.Display;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
@@ -91,10 +91,8 @@
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
-import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
import java.io.File;
import java.io.FileDescriptor;
@@ -103,11 +101,8 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -1739,8 +1734,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
public int onShellCommand(Shell shell, String cmd) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 9d93146..970da99 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -44,9 +44,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -62,6 +60,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -217,7 +216,7 @@
private static final int DEFAULT_FG_JOB_COUNT = 4;
private static final int DEFAULT_BG_NORMAL_JOB_COUNT = 6;
private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4;
- private static final int DEFAULT_BG_LOW_JOB_COUNT = 2;
+ private static final int DEFAULT_BG_LOW_JOB_COUNT = 1;
private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1;
/**
@@ -429,7 +428,18 @@
}
cancelJobsForUid(pkgUid, true);
}
- } catch (RemoteException e) { /* cannot happen */ }
+ } catch (RemoteException|IllegalArgumentException e) {
+ /*
+ * IllegalArgumentException means that the package doesn't exist.
+ * This arises when PACKAGE_CHANGED broadcast delivery has lagged
+ * behind outright uninstall, so by the time we try to act it's gone.
+ * We don't need to act on this PACKAGE_CHANGED when this happens;
+ * we'll get a PACKAGE_REMOVED later and clean up then.
+ *
+ * RemoteException can't actually happen; the package manager is
+ * running in this same process.
+ */
+ }
break;
}
}
@@ -907,7 +917,11 @@
private boolean isCurrentlyActiveLocked(JobStatus job) {
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext serviceContext = mActiveServices.get(i);
- final JobStatus running = serviceContext.getRunningJob();
+ // The 'unsafe' direct-internal-reference running-job inspector is okay to
+ // use here because we are already holding the necessary lock *and* we
+ // immediately discard the returned object reference, if any; we return
+ // only a boolean state indicator to the caller.
+ final JobStatus running = serviceContext.getRunningJobUnsafeLocked();
if (running != null && running.matches(job.getUid(), job.getJobId())) {
return true;
}
@@ -1718,9 +1732,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
};
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 27f397d..b089ba7 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -231,6 +231,15 @@
return job == null ? null : new JobStatus(job);
}
+ /**
+ * Internal non-cloning inspection of the currently running job, if any. The lock
+ * must be held when calling this *and* for the entire lifetime of using its returned
+ * JobStatus object!
+ */
+ JobStatus getRunningJobUnsafeLocked() {
+ return mRunningJob;
+ }
+
/** Called externally when a job that was scheduled for execution should be cancelled. */
void cancelExecutingJob(int reason) {
mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/Light.java
index b18a181..0bab86b 100644
--- a/services/core/java/com/android/server/lights/Light.java
+++ b/services/core/java/com/android/server/lights/Light.java
@@ -16,25 +16,28 @@
package com.android.server.lights;
+import android.hardware.light.V2_0.Flash;
+import android.hardware.light.V2_0.Brightness;
+
public abstract class Light {
- public static final int LIGHT_FLASH_NONE = 0;
- public static final int LIGHT_FLASH_TIMED = 1;
- public static final int LIGHT_FLASH_HARDWARE = 2;
+ public static final int LIGHT_FLASH_NONE = Flash.NONE;
+ public static final int LIGHT_FLASH_TIMED = Flash.TIMED;
+ public static final int LIGHT_FLASH_HARDWARE = Flash.HARDWARE;
/**
* Light brightness is managed by a user setting.
*/
- public static final int BRIGHTNESS_MODE_USER = 0;
+ public static final int BRIGHTNESS_MODE_USER = Brightness.USER;
/**
* Light brightness is managed by a light sensor.
*/
- public static final int BRIGHTNESS_MODE_SENSOR = 1;
+ public static final int BRIGHTNESS_MODE_SENSOR = Brightness.SENSOR;
/**
* Low-persistence light mode.
*/
- public static final int BRIGHTNESS_MODE_LOW_PERSISTENCE = 2;
+ public static final int BRIGHTNESS_MODE_LOW_PERSISTENCE = Brightness.LOW_PERSISTENCE;
public abstract void setBrightness(int brightness);
public abstract void setBrightness(int brightness, int brightnessMode);
diff --git a/services/core/java/com/android/server/lights/LightsManager.java b/services/core/java/com/android/server/lights/LightsManager.java
index 2f20509..be20a44 100644
--- a/services/core/java/com/android/server/lights/LightsManager.java
+++ b/services/core/java/com/android/server/lights/LightsManager.java
@@ -16,16 +16,18 @@
package com.android.server.lights;
+import android.hardware.light.V2_0.Type;
+
public abstract class LightsManager {
- public static final int LIGHT_ID_BACKLIGHT = 0;
- public static final int LIGHT_ID_KEYBOARD = 1;
- public static final int LIGHT_ID_BUTTONS = 2;
- public static final int LIGHT_ID_BATTERY = 3;
- public static final int LIGHT_ID_NOTIFICATIONS = 4;
- public static final int LIGHT_ID_ATTENTION = 5;
- public static final int LIGHT_ID_BLUETOOTH = 6;
- public static final int LIGHT_ID_WIFI = 7;
- public static final int LIGHT_ID_COUNT = 8;
+ public static final int LIGHT_ID_BACKLIGHT = Type.BACKLIGHT;
+ public static final int LIGHT_ID_KEYBOARD = Type.KEYBOARD;
+ public static final int LIGHT_ID_BUTTONS = Type.BUTTONS;
+ public static final int LIGHT_ID_BATTERY = Type.BATTERY;
+ public static final int LIGHT_ID_NOTIFICATIONS = Type.NOTIFICATIONS;
+ public static final int LIGHT_ID_ATTENTION = Type.ATTENTION;
+ public static final int LIGHT_ID_BLUETOOTH = Type.BLUETOOTH;
+ public static final int LIGHT_ID_WIFI = Type.WIFI;
+ public static final int LIGHT_ID_COUNT = Type.COUNT;
public abstract Light getLight(int id);
}
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index ca64817..bba0a50 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -133,7 +133,7 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
+ Integer.toHexString(color) + ")");
try {
- setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
+ setLight_native(mId, color, mode, onMS, offMS, brightnessMode);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -155,8 +155,6 @@
public LightsService(Context context) {
super(context);
- mNativePointer = init_native();
-
for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) {
mLights[i] = new LightImpl(i);
}
@@ -217,7 +215,7 @@
private final LightsManager mService = new LightsManager() {
@Override
public Light getLight(int id) {
- if (id < LIGHT_ID_COUNT) {
+ if (0 <= id && id < LIGHT_ID_COUNT) {
return mLights[id];
} else {
return null;
@@ -225,12 +223,6 @@
}
};
- @Override
- protected void finalize() throws Throwable {
- finalize_native(mNativePointer);
- super.finalize();
- }
-
private Handler mH = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -239,11 +231,6 @@
}
};
- private static native long init_native();
- private static native void finalize_native(long ptr);
-
- static native void setLight_native(long ptr, int light, int color, int mode,
+ static native void setLight_native(int light, int color, int mode,
int onMS, int offMS, int brightnessMode);
-
- private long mNativePointer;
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 7580cf4..9ae496f 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -58,6 +58,7 @@
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -75,6 +76,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Log;
@@ -396,10 +398,6 @@
// Persist property for LPP_PROFILE
private final static String LPP_PROFILE = "persist.sys.gps.lpp";
- // VZW PLMN info
- private static final String[] VzwMccMncList = {"311480", "310004", "20404"};
- // corresponding GID1 value, empty string means ignore gid1 match.
- private static final String[] VzwGid1List = {"", "", "BAE0000000000000"};
private final PowerManager mPowerManager;
@@ -452,8 +450,12 @@
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
- requestUtcTime();
- xtraDownloadRequest();
+ if (mInjectNtpTimePending == STATE_PENDING_NETWORK) {
+ requestUtcTime();
+ }
+ if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) {
+ xtraDownloadRequest();
+ }
}
};
@@ -516,42 +518,30 @@
}
};
- private final boolean isVerizon(String mccMnc, String imsi, String groupId) {
- if (DEBUG) Log.d(TAG, "simOperator: " + mccMnc);
- if (!TextUtils.isEmpty(mccMnc) || !TextUtils.isEmpty(imsi)) {
- for (int i = 0; i < VzwMccMncList.length; i++) {
- if ((!TextUtils.isEmpty(mccMnc) && mccMnc.equals(VzwMccMncList[i])) ||
- (!TextUtils.isEmpty(imsi) && imsi.startsWith(VzwMccMncList[i]))) {
- // check gid too if needed
- if (TextUtils.isEmpty(VzwGid1List[i]) || VzwGid1List[i].equals(groupId)) {
- if (DEBUG) Log.d(TAG, "Verizon UICC");
- return true;
- }
- }
- }
- }
- return false;
- }
-
private void subscriptionOrSimChanged(Context context) {
if (DEBUG) Log.d(TAG, "received SIM related action: ");
TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
String mccMnc = phone.getSimOperator();
- String imsi = phone.getSubscriberId();
- String groupId = phone.getGroupIdLevel1();
+ boolean isKeepLppProfile = false;
if (!TextUtils.isEmpty(mccMnc)) {
if (DEBUG) Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc);
synchronized (mLock) {
- if (isVerizon(mccMnc, imsi, groupId)) {
- // load current properties for carrier VZW
- loadPropertiesFromResource(context, mProperties);
- String lpp_profile = mProperties.getProperty("LPP_PROFILE");
- // set the persist property LPP_PROFILE for VZW
- SystemProperties.set(LPP_PROFILE, lpp_profile);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfig();
+ isKeepLppProfile = b.getBoolean(CarrierConfigManager.KEY_PERSIST_LPP_MODE_BOOL);
+ }
+ if (isKeepLppProfile) {
+ // load current properties for the carrier
+ loadPropertiesFromResource(context, mProperties);
+ String lpp_profile = mProperties.getProperty("LPP_PROFILE");
+ // set the persist property LPP_PROFILE for the value
+ SystemProperties.set(LPP_PROFILE, lpp_profile);
} else {
- // reset the persist property for Non VZW
- SystemProperties.set(LPP_PROFILE, "");
+ // reset the persist property
+ SystemProperties.set(LPP_PROFILE, "");
}
reloadGpsProperties(context, mProperties);
mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
@@ -1002,6 +992,11 @@
}
private void handleDownloadXtraData() {
+ if (!mSupportsXtra) {
+ // native code reports xtra not supported, don't try
+ Log.d(TAG, "handleDownloadXtraData() called when Xtra not supported");
+ return;
+ }
if (mDownloadXtraDataPending == STATE_DOWNLOADING) {
// already downloading data
return;
@@ -2125,9 +2120,7 @@
handleInjectNtpTime();
break;
case DOWNLOAD_XTRA_DATA:
- if (mSupportsXtra) {
- handleDownloadXtraData();
- }
+ handleDownloadXtraData();
break;
case INJECT_NTP_TIME_FINISHED:
mInjectNtpTimePending = STATE_IDLE;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 6381aa7..2bccfee 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -123,7 +123,6 @@
import android.net.NetworkIdentity;
import android.net.NetworkInfo;
import android.net.NetworkPolicy;
-import android.net.NetworkPolicyManager;
import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.NetworkTemplate;
@@ -144,6 +143,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -2046,25 +2046,30 @@
@Override
public void setRestrictBackground(boolean restrictBackground) {
- mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
- final long token = Binder.clearCallingIdentity();
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground");
try {
- maybeRefreshTrustedTime();
- synchronized (mUidRulesFirstLock) {
- if (restrictBackground == mRestrictBackground) {
- // Ideally, UI should never allow this scenario...
- Slog.w(TAG, "setRestrictBackground: already " + restrictBackground);
- return;
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ maybeRefreshTrustedTime();
+ synchronized (mUidRulesFirstLock) {
+ if (restrictBackground == mRestrictBackground) {
+ // Ideally, UI should never allow this scenario...
+ Slog.w(TAG, "setRestrictBackground: already " + restrictBackground);
+ return;
+ }
+ setRestrictBackgroundUL(restrictBackground);
}
- setRestrictBackgroundUL(restrictBackground);
+
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
+ mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED, restrictBackground ? 1 : 0, 0)
+ .sendToTarget();
} finally {
- Binder.restoreCallingIdentity(token);
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
-
- mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED, restrictBackground ? 1 : 0, 0)
- .sendToTarget();
}
private void setRestrictBackgroundUL(boolean restrictBackground) {
@@ -2386,9 +2391,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new NetworkPolicyManagerShellCommand(mContext, this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
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 0c7c8aa..4e50567 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -30,6 +30,7 @@
import static android.service.notification.NotificationRankerService.REASON_PACKAGE_CHANGED;
import static android.service.notification.NotificationRankerService.REASON_PACKAGE_SUSPENDED;
import static android.service.notification.NotificationRankerService.REASON_PROFILE_TURNED_OFF;
+import static android.service.notification.NotificationRankerService.REASON_SNOOZED;
import static android.service.notification.NotificationRankerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationRankerService.REASON_USER_STOPPED;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -309,6 +310,9 @@
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
private String mSystemNotificationSound;
+ private SnoozeHelper mSnoozeHelper;
+ private GroupHelper mGroupHelper;
+
private static class Archive {
final int mBufferSize;
final ArrayDeque<StatusBarNotification> mBuffer;
@@ -999,6 +1003,51 @@
sendRegisteredOnlyBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED);
}
});
+ mSnoozeHelper = new SnoozeHelper(getContext(), new SnoozeHelper.Callback() {
+ @Override
+ public void repost(int userId, NotificationRecord r) {
+ try {
+ if (DBG) {
+ Slog.d(TAG, "Reposting " + r.getKey());
+ }
+ enqueueNotificationInternal(r.sbn.getPackageName(), r.sbn.getOpPkg(),
+ r.sbn.getUid(), r.sbn.getInitialPid(), r.sbn.getTag(), r.sbn.getId(),
+ r.sbn.getNotification(), new int[1], userId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Cannot un-snooze notification", e);
+ }
+ }
+ }, 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"));
@@ -1810,6 +1859,13 @@
}
}
+ /**
+ * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
+ *
+ * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
@@ -1819,6 +1875,23 @@
}
/**
+ * Allow an INotificationListener to snooze a single notification.
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ @Override
+ public void snoozeNotificationFromListener(INotificationListener token, String key,
+ long snoozeUntil) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ snoozeNotificationInt(key, snoozeUntil, info);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Allow an INotificationListener to simulate clearing (dismissing) a single notification.
*
* {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
@@ -2307,7 +2380,6 @@
mRankerServices.checkServiceTokenLocked(token);
applyAdjustmentLocked(adjustment);
}
- maybeAddAutobundleSummary(adjustment);
mRankingHandler.requestSort();
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2326,9 +2398,6 @@
applyAdjustmentLocked(adjustment);
}
}
- for (Adjustment adjustment : adjustments) {
- maybeAddAutobundleSummary(adjustment);
- }
mRankingHandler.requestSort();
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2337,7 +2406,6 @@
};
private void applyAdjustmentLocked(Adjustment adjustment) {
- maybeClearAutobundleSummaryLocked(adjustment);
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
if (n == null) {
return;
@@ -2347,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));
}
}
@@ -2592,6 +2650,11 @@
pw.println("\n Notification ranker services:");
mRankerServices.dump(pw, filter);
}
+
+ if (!zenOnly) {
+ mSnoozeHelper.dump(pw, filter);
+ }
+
pw.println("\n Policy access:");
pw.print(" mPolicyAccess: "); pw.println(mPolicyAccess);
@@ -2642,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);
+ }
+ });
}
}
};
@@ -2765,6 +2834,16 @@
public void run() {
synchronized (mNotificationList) {
+ if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) {
+ // TODO: log to event log
+ if (DBG) {
+ Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
+ }
+ mSnoozeHelper.update(userId, r);
+ savePolicyFile();
+ return;
+ }
+
final StatusBarNotification n = r.sbn;
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
NotificationRecord old = mNotificationsByKey.get(n.getKey());
@@ -2856,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
@@ -3467,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();
@@ -3578,6 +3675,11 @@
cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
REASON_GROUP_SUMMARY_CANCELED, sendDelete);
updateLightsLocked();
+ } else {
+ final boolean wasSnoozed = mSnoozeHelper.cancel(userId, pkg, tag, id);
+ if (wasSnoozed) {
+ savePolicyFile();
+ }
}
}
}
@@ -3641,7 +3743,7 @@
if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
continue;
}
- if (channelId == null || !channelId.equals(r.getChannel().getId())) {
+ if (channelId != null && !channelId.equals(r.getChannel().getId())) {
continue;
}
if (canceledNotifications == null) {
@@ -3654,6 +3756,7 @@
mNotificationList.remove(i);
cancelNotificationLocked(r, false, reason);
}
+ mSnoozeHelper.cancel(userId, pkg);
if (doit && canceledNotifications != null) {
final int M = canceledNotifications.size();
for (int i = 0; i < M; i++) {
@@ -3668,6 +3771,28 @@
}
}
+ void snoozeNotificationInt(String key, long until, ManagedServiceInfo listener) {
+ String listenerName = listener == null ? null : listener.component.toShortString();
+ // TODO: write to event log
+ if (DBG) {
+ Slog.d(TAG, String.format("snooze event(%s, %d, %s)", key, until,
+ listenerName));
+ }
+ if (until < System.currentTimeMillis()) {
+ return;
+ }
+ synchronized (mNotificationList) {
+ final NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ mNotificationList.remove(r);
+ cancelNotificationLocked(r, false, REASON_SNOOZED);
+ updateLightsLocked();
+ mSnoozeHelper.snooze(r, r.getUser().getIdentifier(), until);
+ savePolicyFile();
+ }
+ }
+ }
+
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
ManagedServiceInfo listener, boolean includeCurrentProfiles) {
String listenerName = listener == null ? null : listener.component.toShortString();
@@ -3699,6 +3824,7 @@
canceledNotifications.add(r);
}
}
+ mSnoozeHelper.cancel(userId, includeCurrentProfiles);
int M = canceledNotifications != null ? canceledNotifications.size() : 0;
for (int i = 0; i < M; i++) {
cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7182da1..d59115e 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -162,6 +162,7 @@
r = mRestoredWithoutUids.get(name);
if (r == null) {
r = new Record();
+ r.pkg = name;
mRestoredWithoutUids.put(name, r);
}
} else {
@@ -606,7 +607,7 @@
}
public void onPackagesChanged(boolean removingPackage, String[] pkgList) {
- if (!removingPackage || pkgList == null || pkgList.length == 0
+ if (removingPackage || pkgList == null || pkgList.length == 0
|| mRestoredWithoutUids.isEmpty()) {
return; // nothing to do
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
new file mode 100644
index 0000000..738403e
--- /dev/null
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -0,0 +1,281 @@
+/*
+ * 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 com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * NotificationManagerService helper for handling snoozed notifications.
+ */
+public class SnoozeHelper {
+ private static final String TAG = "SnoozeHelper";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String INDENT = " ";
+
+ private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE";
+ private static final int REQUEST_CODE_REPOST = 1;
+ private static final String REPOST_SCHEME = "repost";
+ private static final String EXTRA_PKG = "pkg";
+ private static final String EXTRA_KEY = "key";
+ private static final String EXTRA_USER_ID = "userId";
+
+ private final Context mContext;
+ private AlarmManager mAm;
+ private final ManagedServices.UserProfiles mUserProfiles;
+
+ // User id : package name : notification key : record.
+ private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>>
+ mSnoozedNotifications = new ArrayMap<>();
+ private Callback mCallback;
+
+ public SnoozeHelper(Context context, Callback callback,
+ ManagedServices.UserProfiles userProfiles) {
+ mContext = context;
+ IntentFilter filter = new IntentFilter(REPOST_ACTION);
+ filter.addDataScheme(REPOST_SCHEME);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+ mAm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mCallback = callback;
+ mUserProfiles = userProfiles;
+ }
+
+ protected boolean isSnoozed(int userId, String pkg, String key) {
+ return mSnoozedNotifications.containsKey(userId)
+ && mSnoozedNotifications.get(userId).containsKey(pkg)
+ && mSnoozedNotifications.get(userId).get(pkg).containsKey(key);
+ }
+
+ /**
+ * Records a notification that should be snoozed until the given time and schedules an alarm
+ * to repost at that time.
+ */
+ protected void snooze(NotificationRecord record, int userId, long until) {
+ ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
+ mSnoozedNotifications.get(userId);
+ if (records == null) {
+ records = new ArrayMap<>();
+ }
+ ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.sbn.getPackageName());
+ if (pkgRecords == null) {
+ pkgRecords = new ArrayMap<>();
+ }
+ pkgRecords.put(record.getKey(), record);
+ records.put(record.sbn.getPackageName(), pkgRecords);
+ mSnoozedNotifications.put(userId, records);
+ if (DEBUG) {
+ Slog.d(TAG, "Snoozing " + record.getKey() + " until " + new Date(until));
+ }
+ scheduleRepost(record.sbn.getPackageName(), record.getKey(), userId, until);
+ }
+
+
+ protected boolean cancel(int userId, String pkg, String tag, int id) {
+ if (mSnoozedNotifications.containsKey(userId)) {
+ ArrayMap<String, NotificationRecord> recordsForPkg =
+ mSnoozedNotifications.get(userId).get(pkg);
+ if (recordsForPkg != null) {
+ final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
+ String key = null;
+ for (Map.Entry<String, NotificationRecord> record : records) {
+ final StatusBarNotification sbn = record.getValue().sbn;
+ if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
+ key = record.getKey();
+ }
+ }
+ if (key != null) {
+ recordsForPkg.remove(key);
+ cancelAlarm(userId, pkg, key);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean cancel(int userId, boolean includeCurrentProfiles) {
+ int[] userIds = {userId};
+ if (includeCurrentProfiles) {
+ userIds = mUserProfiles.getCurrentProfileIds();
+ }
+ final int N = userIds.length;
+ for (int i = 0; i < N; i++) {
+ final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
+ mSnoozedNotifications.remove(userIds[i]);
+ if (snoozedPkgs != null) {
+ final int M = snoozedPkgs.size();
+ for (int j = 0; j < M; j++) {
+ final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
+ if (records != null) {
+ int P = records.size();
+ for (int k = 0; k < P; k++) {
+ cancelAlarm(userId, snoozedPkgs.keyAt(j), records.keyAt(k));
+ }
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean cancel(int userId, String pkg) {
+ if (mSnoozedNotifications.containsKey(userId)) {
+ if (mSnoozedNotifications.get(userId).containsKey(pkg)) {
+ ArrayMap<String, NotificationRecord> records =
+ mSnoozedNotifications.get(userId).remove(pkg);
+ int N = records.size();
+ for (int i = 0; i < N; i++) {
+ cancelAlarm(userId, pkg, records.keyAt(i));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void cancelAlarm(int userId, String pkg, String key) {
+ final PendingIntent pi = createPendingIntent(pkg, key, userId);
+ mAm.cancel(pi);
+ }
+
+ /**
+ * Updates the notification record so the most up to date information is shown on re-post.
+ */
+ protected void update(int userId, NotificationRecord record) {
+ ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
+ mSnoozedNotifications.get(userId);
+ if (records == null) {
+ return;
+ }
+ ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.sbn.getPackageName());
+ if (pkgRecords == null) {
+ return;
+ }
+ pkgRecords.put(record.getKey(), record);
+ }
+
+ @VisibleForTesting
+ void repost(String pkg, String key, int userId) {
+ ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
+ mSnoozedNotifications.get(userId);
+ if (records == null) {
+ return;
+ }
+ ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
+ if (pkgRecords == null) {
+ return;
+ }
+ final NotificationRecord record = pkgRecords.remove(key);
+ if (record != null) {
+ mCallback.repost(userId, record);
+ }
+ }
+
+ private PendingIntent createPendingIntent(String pkg, String key, int userId) {
+ return PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_REPOST,
+ new Intent(REPOST_ACTION)
+ .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_PKG, pkg)
+ .putExtra(EXTRA_KEY, key)
+ .putExtra(EXTRA_USER_ID, userId),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ private void scheduleRepost(String pkg, String key, int userId, long time) {
+ final PendingIntent pi = createPendingIntent(pkg, key, userId);
+ mAm.cancel(pi);
+ if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time));
+ mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi);
+ }
+
+ public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
+ pw.println("\n Snoozed notifications:");
+ for (int userId : mSnoozedNotifications.keySet()) {
+ pw.print(INDENT);
+ pw.println("user: " + userId);
+ ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
+ mSnoozedNotifications.get(userId);
+ for (String pkg : snoozedPkgs.keySet()) {
+ pw.print(INDENT);
+ pw.print(INDENT);
+ pw.println("package: " + pkg);
+ Set<String> snoozedKeys = snoozedPkgs.get(pkg).keySet();
+ for (String key : snoozedKeys) {
+ pw.print(INDENT);
+ pw.print(INDENT);
+ pw.print(INDENT);
+ pw.println(key);
+ }
+ }
+ }
+ }
+
+ protected void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
+
+ }
+
+ public void readXml(XmlPullParser parser, boolean forRestore)
+ throws XmlPullParserException, IOException {
+
+ }
+
+ @VisibleForTesting
+ void setAlarmManager(AlarmManager am) {
+ mAm = am;
+ }
+
+ protected interface Callback {
+ void repost(int userId, NotificationRecord r);
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Reposting notification");
+ }
+ if (REPOST_ACTION.equals(intent.getAction())) {
+ repost(intent.getStringExtra(EXTRA_PKG), intent.getStringExtra(EXTRA_KEY),
+ intent.getIntExtra(EXTRA_USER_ID, 0));
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
index 759030b..5f08257 100644
--- a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
+++ b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Build;
import android.os.IDeviceIdentifiersPolicyService;
@@ -53,9 +54,15 @@
@Override
public @Nullable String getSerial() throws RemoteException {
- if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.READ_PHONE_STATE, "getSerial");
+ if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID
+ && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.READ_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("getSerial requires READ_PHONE_STATE"
+ + " or READ_PRIVILEGED_PHONE_STATE permission");
}
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
}
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index ded85f3..00e45fd 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -150,6 +150,8 @@
private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
+ private static final String ACTION_TRACK = "com.android.fitness.TRACK";
+
private final PackageManagerService mService;
private final Handler mHandler;
@@ -603,8 +605,9 @@
grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
}
- // Android Wear Home
+ // Watches
if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
+ // Home application on watches
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
@@ -621,6 +624,16 @@
grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
userId);
}
+
+ // Fitness tracking on watches
+ Intent trackIntent = new Intent(ACTION_TRACK);
+ PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackageLPr(
+ trackIntent, userId);
+ if (trackPackage != null
+ && doesPackageSupportRuntimePermissions(trackPackage)) {
+ grantRuntimePermissionsLPw(trackPackage, SENSORS_PERMISSIONS, false, userId);
+ grantRuntimePermissionsLPw(trackPackage, LOCATION_PERMISSIONS, false, userId);
+ }
}
// Print Spooler
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 689917c..f777aae 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -28,6 +28,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.storage.StorageManager;
import android.util.Log;
import android.util.Slog;
@@ -107,9 +108,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new OtaDexoptShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0b8a347..2ece99f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -439,7 +439,13 @@
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid name: " + name);
}
- final File target = new File(resolveStageDir(), name);
+ final File target;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ target = new File(resolveStageDir(), name);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
// TODO: this should delegate to DCS so the system process avoids
// holding open FDs into containers.
@@ -1084,7 +1090,12 @@
if (stageDir != null) {
prepareStageDir(stageDir);
} else if (stageCid != null) {
- prepareExternalStageCid(stageCid, params.sizeBytes);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ prepareExternalStageCid(stageCid, params.sizeBytes);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
// TODO: deliver more granular progress for ASEC allocation
mInternalProgress = 0.25f;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c76302c..974d95d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -185,6 +185,7 @@
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
@@ -464,6 +465,12 @@
private static final String PACKAGE_SCHEME = "package";
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
+ /**
+ * 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_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;
@@ -2278,12 +2285,17 @@
}
}
- // Collect vendor overlay packages.
- // (Do this before scanning any apps.)
+ // Collect vendor overlay packages. (Do this before scanning any apps.)
// For security and version matching reason, only consider
- // overlay packages if they reside in VENDOR_OVERLAY_DIR.
- File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
- scanDirTracedLI(vendorOverlayDir, mDefParseFlags
+ // overlay packages if they reside in the right directory.
+ 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);
+ }
+ scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
@@ -2526,8 +2538,7 @@
// NOTE: We ignore potential failures here during a system scan (like
// the rest of the commands above) because there's precious little we
// can do about it. A settings error is reported, though.
- adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
- false /* boot complete */);
+ adjustCpuAbisForSharedUserLPw(setting.packages, null /*scannedPackage*/);
}
// Now that we know all the packages we are keeping,
@@ -4000,6 +4011,11 @@
@Override
public void grantRuntimePermission(String packageName, String name, final int userId) {
+ grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
+ }
+
+ private void grantRuntimePermission(String packageName, String name, final int userId,
+ boolean overridePolicy) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
@@ -4052,6 +4068,10 @@
throw new SecurityException("Cannot grant system fixed permission "
+ name + " for package " + packageName);
}
+ if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ throw new SecurityException("Cannot grant policy fixed permission "
+ + name + " for package " + packageName);
+ }
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
@@ -4112,6 +4132,11 @@
@Override
public void revokeRuntimePermission(String packageName, String name, int userId) {
+ revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
+ }
+
+ private void revokeRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
@@ -4162,6 +4187,10 @@
throw new SecurityException("Cannot revoke system fixed permission "
+ name + " for package " + packageName);
}
+ if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ throw new SecurityException("Cannot revoke policy fixed permission "
+ + name + " for package " + packageName);
+ }
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
@@ -4697,11 +4726,16 @@
}
// reader
synchronized (mPackages) {
- final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, 0, false);
- if (suid == null) {
- return -1;
+ SharedUserSetting suid;
+ try {
+ suid = mSettings.getSharedUserLPw(sharedUserName, 0, 0, false);
+ if (suid != null) {
+ return suid.userId;
+ }
+ } catch (PackageManagerException ignore) {
+ // can't happen, but, still need to catch it
}
- return suid.userId;
+ return -1;
}
}
@@ -6864,11 +6898,11 @@
if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
// This package has been renamed to its original name. Let's
// use that.
- ps = mSettings.peekPackageLPr(oldName);
+ ps = mSettings.getPackageLPr(oldName);
}
// If there was no original package, see one for the real package name.
if (ps == null) {
- ps = mSettings.peekPackageLPr(pkg.packageName);
+ ps = mSettings.getPackageLPr(pkg.packageName);
}
// Check to see if this package could be hiding/updating a system
// package. Must look for it either under the original or real
@@ -7108,7 +7142,7 @@
}
private static String fixProcessName(String defProcessName,
- String processName, int uid) {
+ String processName) {
if (processName == null) {
return defProcessName;
}
@@ -7780,7 +7814,7 @@
}
}
- private void addSharedLibraryLPw(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
+ private void addSharedLibraryLPr(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
PackageParser.Package changingLib) {
if (file.path != null) {
usesLibraryFiles.add(file.path);
@@ -7801,7 +7835,7 @@
}
}
- private void updateSharedLibrariesLPw(PackageParser.Package pkg,
+ private void updateSharedLibrariesLPr(PackageParser.Package pkg,
PackageParser.Package changingLib) throws PackageManagerException {
if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
final ArraySet<String> usesLibraryFiles = new ArraySet<>();
@@ -7813,7 +7847,7 @@
"Package " + pkg.packageName + " requires unavailable shared library "
+ pkg.usesLibraries.get(i) + "; failing!");
}
- addSharedLibraryLPw(usesLibraryFiles, file, changingLib);
+ addSharedLibraryLPr(usesLibraryFiles, file, changingLib);
}
N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
for (int i=0; i<N; i++) {
@@ -7823,7 +7857,7 @@
+ " desires unavailable shared library "
+ pkg.usesOptionalLibraries.get(i) + "; ignoring!");
} else {
- addSharedLibraryLPw(usesLibraryFiles, file, changingLib);
+ addSharedLibraryLPr(usesLibraryFiles, file, changingLib);
}
}
N = usesLibraryFiles.size();
@@ -7852,7 +7886,7 @@
private void updateAllSharedLibrariesLPw() {
for (PackageParser.Package pkg : mPackages.values()) {
try {
- updateSharedLibrariesLPw(pkg, null);
+ updateSharedLibrariesLPr(pkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
@@ -7870,7 +7904,7 @@
}
res.add(pkg);
try {
- updateSharedLibrariesLPw(pkg, changingPkg);
+ updateSharedLibrariesLPr(pkg, changingPkg);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
@@ -7981,7 +8015,7 @@
*
* @throws PackageManagerException If bytecode could not be found when it should exist
*/
- private static void enforceCodePolicy(PackageParser.Package pkg)
+ private static void assertCodePolicy(PackageParser.Package pkg)
throws PackageManagerException {
final boolean shouldHaveCode =
(pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
@@ -8004,164 +8038,24 @@
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
- throws PackageManagerException {
- final File scanFile = new File(pkg.codePath);
- if (pkg.applicationInfo.getCodePath() == null ||
- pkg.applicationInfo.getResourcePath() == null) {
- // Bail out. The resource and code paths haven't been set.
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
- "Code and resource paths haven't been set correctly");
- }
-
- // Apply policy
- if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
- pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- if (pkg.applicationInfo.isDirectBootAware()) {
- // we're direct boot aware; set for all components
- for (PackageParser.Service s : pkg.services) {
- s.info.encryptionAware = s.info.directBootAware = true;
- }
- for (PackageParser.Provider p : pkg.providers) {
- p.info.encryptionAware = p.info.directBootAware = true;
- }
- for (PackageParser.Activity a : pkg.activities) {
- a.info.encryptionAware = a.info.directBootAware = true;
- }
- for (PackageParser.Activity r : pkg.receivers) {
- r.info.encryptionAware = r.info.directBootAware = true;
- }
- }
- } else {
- // Only allow system apps to be flagged as core apps.
- pkg.coreApp = false;
- // clear flags not applicable to regular apps
- pkg.applicationInfo.privateFlags &=
- ~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
- pkg.applicationInfo.privateFlags &=
- ~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
- }
- pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0;
-
- if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
- pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- }
-
- if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
- enforceCodePolicy(pkg);
- }
-
- if (mCustomResolverComponentName != null &&
- mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
- setUpCustomResolverActivity(pkg);
- }
-
- if (pkg.packageName.equals("android")) {
- synchronized (mPackages) {
- if (mAndroidApplication != null) {
- Slog.w(TAG, "*************************************************");
- Slog.w(TAG, "Core android package being redefined. Skipping.");
- Slog.w(TAG, " file=" + scanFile);
- Slog.w(TAG, "*************************************************");
- throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
- "Core android package being redefined. Skipping.");
- }
-
- if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
- // Set up information for our fall-back user intent resolution activity.
- mPlatformPackage = pkg;
- pkg.mVersionCode = mSdkVersion;
- mAndroidApplication = pkg.applicationInfo;
-
- if (!mResolverReplaced) {
- mResolveActivity.applicationInfo = mAndroidApplication;
- mResolveActivity.name = ResolverActivity.class.getName();
- mResolveActivity.packageName = mAndroidApplication.packageName;
- mResolveActivity.processName = "system:ui";
- mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
- mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
- mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
- mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
- mResolveActivity.exported = true;
- mResolveActivity.enabled = true;
- mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
- mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
- | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
- | ActivityInfo.CONFIG_SCREEN_LAYOUT
- | ActivityInfo.CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_KEYBOARD
- | ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
- mResolveInfo.activityInfo = mResolveActivity;
- mResolveInfo.priority = 0;
- mResolveInfo.preferredOrder = 0;
- mResolveInfo.match = 0;
- mResolveComponentName = new ComponentName(
- mAndroidApplication.packageName, mResolveActivity.name);
- }
- }
- }
- }
-
+ throws PackageManagerException {
if (DEBUG_PACKAGE_SCANNING) {
if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Scanning package " + pkg.packageName);
}
- synchronized (mPackages) {
- if (mPackages.containsKey(pkg.packageName)
- || mSharedLibraries.containsKey(pkg.packageName)) {
- throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
- "Application package " + pkg.packageName
- + " already installed. Skipping duplicate.");
- }
+ applyPolicy(pkg, policyFlags);
- // If we're only installing presumed-existing packages, require that the
- // scanned APK is both already known and at the path previously established
- // for it. Previously unknown packages we pick up normally, but if we have an
- // a priori expectation about this package's install presence, enforce it.
- // With a singular exception for new system packages. When an OTA contains
- // a new system package, we allow the codepath to change from a system location
- // to the user-installed location. If we don't allow this change, any newer,
- // user-installed version of the application will be ignored.
- if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
- if (mExpectingBetter.containsKey(pkg.packageName)) {
- logCriticalInfo(Log.WARN,
- "Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);
- } else {
- PackageSetting known = mSettings.peekPackageLPr(pkg.packageName);
- if (known != null) {
- if (DEBUG_PACKAGE_SCANNING) {
- Log.d(TAG, "Examining " + pkg.codePath
- + " and requiring known paths " + known.codePathString
- + " & " + known.resourcePathString);
- }
- if (!pkg.applicationInfo.getCodePath().equals(known.codePathString)
- || !pkg.applicationInfo.getResourcePath().equals(
- known.resourcePathString)) {
- throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
- "Application package " + pkg.packageName
- + " found at " + pkg.applicationInfo.getCodePath()
- + " but expected at " + known.codePathString
- + "; ignoring.");
- }
- }
- }
- }
- }
+ assertPackageIsValid(pkg, policyFlags);
// Initialize package source and resource directories
- File destCodeFile = new File(pkg.applicationInfo.getCodePath());
- File destResourceFile = new File(pkg.applicationInfo.getResourcePath());
+ final File scanFile = new File(pkg.codePath);
+ final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
+ final File destResourceFile = new File(pkg.applicationInfo.getResourcePath());
SharedUserSetting suid = null;
PackageSetting pkgSetting = null;
- if (!isSystemApp(pkg)) {
- // Only system apps can use these features.
- pkg.mOriginalPackages = null;
- pkg.mRealPackage = null;
- pkg.mAdoptPermissions = null;
- }
-
// Getting the package setting may have a side-effect, so if we
// are only checking if scan would succeed, stash a copy of the
// old setting to restore at the end.
@@ -8170,12 +8064,9 @@
// writer
synchronized (mPackages) {
if (pkg.mSharedUserId != null) {
- suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);
- if (suid == null) {
- throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
- "Creating application package " + pkg.packageName
- + " for shared user failed");
- }
+ // SIDE EFFECTS; may potentially allocate a new shared user
+ suid = mSettings.getSharedUserLPw(
+ pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
if (DEBUG_PACKAGE_SCANNING) {
if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId
@@ -8202,10 +8093,9 @@
// it is not already done.
pkg.setPackageName(renamed);
}
-
} else {
for (int i=pkg.mOriginalPackages.size()-1; i>=0; i--) {
- if ((origPackage = mSettings.peekPackageLPr(
+ if ((origPackage = mSettings.getPackageLPr(
pkg.mOriginalPackages.get(i))) != null) {
// We do have the package already installed under its
// original name... should we use it?
@@ -8241,7 +8131,7 @@
// See comments in nonMutatedPs declaration
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
- PackageSetting foundPs = mSettings.peekPackageLPr(pkg.packageName);
+ PackageSetting foundPs = mSettings.getPackageLPr(pkg.packageName);
if (foundPs != null) {
nonMutatedPs = new PackageSetting(foundPs);
}
@@ -8251,10 +8141,11 @@
if (pkgSetting != null && pkgSetting.sharedUser != suid) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Package " + pkg.packageName + " shared user changed from "
- + (pkgSetting.sharedUser != null ? pkgSetting.sharedUser.name : "<nothing>")
- + " to "
- + (suid != null ? suid.name : "<nothing>")
- + "; replacing with new");
+ + (pkgSetting.sharedUser != null
+ ? pkgSetting.sharedUser.name : "<nothing>")
+ + " to "
+ + (suid != null ? suid.name : "<nothing>")
+ + "; replacing with new");
pkgSetting = null;
}
final PackageSetting oldPkgSetting =
@@ -8264,6 +8155,7 @@
if (pkgSetting == null) {
final String parentPackageName = (pkg.parentPackage != null)
? pkg.parentPackage.packageName : null;
+ // REMOVE SharedUserSetting from method; update in a separate call
pkgSetting = Settings.createNewSetting(pkg.packageName, origPackage,
disabledPkgSetting, realName, suid, destCodeFile, destResourceFile,
pkg.applicationInfo.nativeLibraryRootDir, pkg.applicationInfo.primaryCpuAbi,
@@ -8271,19 +8163,23 @@
pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, user,
true /*allowInstall*/, parentPackageName, pkg.getChildPackageNames(),
UserManagerService.getInstance());
+ // SIDE EFFECTS; updates system state; move elsewhere
if (origPackage != null) {
mSettings.addRenamedPackageLPw(pkg.packageName, origPackage.name);
}
mSettings.addUserToSettingLPw(pkgSetting);
} else {
+ // REMOVE SharedUserSetting from method; update in a separate call
Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, suid, destCodeFile,
pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.primaryCpuAbi,
pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags,
pkg.applicationInfo.privateFlags, pkg.getChildPackageNames(),
UserManagerService.getInstance());
}
- mSettings.writeUserRestrictions(pkgSetting, oldPkgSetting);
+ // SIDE EFFECTS; persists system state to files on disk; move elsewhere
+ mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting);
+ // SIDE EFFECTS; modifies system state; move elsewhere
if (pkgSetting.origPackage != null) {
// If we are first transitioning from an original package,
// fix up the new package's name now. We need to do this after
@@ -8305,6 +8201,7 @@
pkgSetting.origPackage = null;
}
+ // SIDE EFFECTS; modifies system state; move elsewhere
if ((scanFlags & SCAN_CHECK_ONLY) == 0 && realName != null) {
// Make a note of it.
mTransferedPackages.add(pkg.packageName);
@@ -8314,13 +8211,13 @@
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
}
- if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
// will take care of the system apps by updating all of their
// library paths after the scan is done.
- updateSharedLibrariesLPw(pkg, null);
+ updateSharedLibrariesLPr(pkg, null);
}
if (mFoundPolicyFile) {
@@ -8342,12 +8239,13 @@
} else {
pkgSetting.signatures.mSignatures = pkg.mSignatures;
String msg = "System package " + pkg.packageName
- + " signature changed; retaining data.";
+ + " signature changed; retaining data.";
reportSettingsProblem(Log.WARN, msg);
}
}
} else {
try {
+ // SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService
verifySignaturesLP(pkgSetting, pkg);
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
@@ -8366,57 +8264,31 @@
// that unreasonable.
if (pkgSetting.sharedUser != null) {
if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+ pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
throw new PackageManagerException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
- "Signature mismatch for shared user: "
+ "Signature mismatch for shared user: "
+ pkgSetting.sharedUser);
}
}
// File a report about this.
String msg = "System package " + pkg.packageName
- + " signature changed; retaining data.";
+ + " signature changed; retaining data.";
reportSettingsProblem(Log.WARN, msg);
}
}
- // Verify that this new package doesn't have any content providers
- // that conflict with existing packages. Only do this if the
- // package isn't already installed, since we don't want to break
- // things that are installed.
- if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
- final int N = pkg.providers.size();
- int i;
- for (i=0; i<N; i++) {
- PackageParser.Provider p = pkg.providers.get(i);
- if (p.info.authority != null) {
- String names[] = p.info.authority.split(";");
- for (int j = 0; j < names.length; j++) {
- if (mProvidersByAuthority.containsKey(names[j])) {
- PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
- final String otherPackageName =
- ((other != null && other.getComponentName() != null) ?
- other.getComponentName().getPackageName() : "?");
- throw new PackageManagerException(
- INSTALL_FAILED_CONFLICTING_PROVIDER,
- "Can't install because provider name " + names[j]
- + " (in package " + pkg.applicationInfo.packageName
- + ") is already used by " + otherPackageName);
- }
- }
- }
- }
- }
if ((scanFlags & SCAN_CHECK_ONLY) == 0 && pkg.mAdoptPermissions != null) {
// This package wants to adopt ownership of permissions from
// another package.
for (int i = pkg.mAdoptPermissions.size() - 1; i >= 0; i--) {
final String origName = pkg.mAdoptPermissions.get(i);
- final PackageSetting orig = mSettings.peekPackageLPr(origName);
+ final PackageSetting orig = mSettings.getPackageLPr(origName);
if (orig != null) {
if (verifyPackageUpdateLPr(orig, pkg)) {
Slog.i(TAG, "Adopting permissions from " + origName + " to "
+ pkg.packageName);
+ // SIDE EFFECTS; updates permissions system state; move elsewhere
mSettings.transferPermissionsLPw(origName, pkg.packageName);
}
}
@@ -8424,26 +8296,21 @@
}
}
- final String pkgName = pkg.packageName;
-
- final long scanFileTime = getLastModifiedTime(pkg, scanFile);
- final boolean forceDex = (scanFlags & SCAN_FORCE_DEX) != 0;
pkg.applicationInfo.processName = fixProcessName(
pkg.applicationInfo.packageName,
- pkg.applicationInfo.processName,
- pkg.applicationInfo.uid);
+ pkg.applicationInfo.processName);
if (pkg != mPlatformPackage) {
// Get all of our default paths setup
pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);
}
- final String path = scanFile.getPath();
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
- derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /*extractLibs*/);
+ derivePackageAbi(
+ pkg, scanFile, cpuAbiOverride, true /*extractLibs*/, mAppLib32InstallDir);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Some system apps still use directory structure for native libraries
@@ -8452,9 +8319,8 @@
if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
pkg.applicationInfo.primaryCpuAbi == null) {
setBundledAppAbisAndRoots(pkg, pkgSetting);
- setNativeLibraryPaths(pkg);
+ setNativeLibraryPaths(pkg, mAppLib32InstallDir);
}
-
} else {
if ((scanFlags & SCAN_MOVE) != 0) {
// We haven't run dex-opt for this move (since we've moved the compiled output too)
@@ -8468,7 +8334,7 @@
// ABIs we've determined above. For non-moves, the path will be updated based on the
// ABIs we determined during compilation, but the path will depend on the final
// package path (after the rename away from the stage path).
- setNativeLibraryPaths(pkg);
+ setNativeLibraryPaths(pkg, mAppLib32InstallDir);
}
// This is a special case for the "system" package, where the ABI is
@@ -8515,6 +8381,7 @@
" secondary=" + pkg.applicationInfo.secondaryCpuAbi);
}
+ // SIDE EFFECTS; removes DEX files from disk; move elsewhere
if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
// We don't do this here during boot because we can do it all
// at once after scanning all existing packages.
@@ -8522,8 +8389,7 @@
// We also do this *before* we perform dexopt on this package, so that
// we can avoid redundant dexopts, and also to make sure we've got the
// code and package path correct.
- adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
- pkg, true /* boot complete */);
+ adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages, pkg);
}
if (mFactoryTest && pkg.requestedPermissions.contains(
@@ -8535,7 +8401,25 @@
pkgSetting.isOrphaned = true;
}
- ArrayList<PackageParser.Package> clientLibPkgs = null;
+ // Take care of first install / last update times.
+ final long scanFileTime = getLastModifiedTime(pkg, scanFile);
+ if (currentTime != 0) {
+ if (pkgSetting.firstInstallTime == 0) {
+ pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
+ } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
+ pkgSetting.lastUpdateTime = currentTime;
+ }
+ } else if (pkgSetting.firstInstallTime == 0) {
+ // We need *something*. Take time time stamp of the file.
+ pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
+ } else if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+ if (scanFileTime != pkgSetting.timeStamp) {
+ // A package on the system image has changed; consider this
+ // to be an update.
+ pkgSetting.lastUpdateTime = scanFileTime;
+ }
+ }
+ pkgSetting.setTimeStamp(scanFileTime);
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
if (nonMutatedPs != null) {
@@ -8543,27 +8427,239 @@
mSettings.mPackages.put(nonMutatedPs.name, nonMutatedPs);
}
}
- return pkg;
+ } else {
+ // Modify state for the given package setting
+ commitPackageSettings(pkg, pkgSetting, user, scanFlags,
+ (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
+ }
+ return pkg;
+ }
+
+ /**
+ * Applies policy to the parsed package based upon the given policy flags.
+ * Ensures the package is in a good state.
+ * <p>
+ * Implementation detail: This method must NOT have any side effect. It would
+ * ideally be static, but, it requires locks to read system state.
+ */
+ private void applyPolicy(PackageParser.Package pkg, int policyFlags) {
+ if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ if (pkg.applicationInfo.isDirectBootAware()) {
+ // we're direct boot aware; set for all components
+ for (PackageParser.Service s : pkg.services) {
+ s.info.encryptionAware = s.info.directBootAware = true;
+ }
+ for (PackageParser.Provider p : pkg.providers) {
+ p.info.encryptionAware = p.info.directBootAware = true;
+ }
+ for (PackageParser.Activity a : pkg.activities) {
+ a.info.encryptionAware = a.info.directBootAware = true;
+ }
+ for (PackageParser.Activity r : pkg.receivers) {
+ r.info.encryptionAware = r.info.directBootAware = true;
+ }
+ }
+ } else {
+ // Only allow system apps to be flagged as core apps.
+ pkg.coreApp = false;
+ // clear flags not applicable to regular apps
+ pkg.applicationInfo.privateFlags &=
+ ~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
+ pkg.applicationInfo.privateFlags &=
+ ~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+ }
+ pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0;
+
+ if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
+ pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
- // Only privileged apps and updated privileged apps can add child packages.
- if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
- if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
- throw new PackageManagerException("Only privileged apps and updated "
- + "privileged apps can add child packages. Ignoring package "
- + pkg.packageName);
+ if (!isSystemApp(pkg)) {
+ // Only system apps can use these features.
+ pkg.mOriginalPackages = null;
+ pkg.mRealPackage = null;
+ pkg.mAdoptPermissions = null;
+ }
+ }
+
+ /**
+ * Asserts the parsed package is valid according to teh given policy. If the
+ * package is invalid, for whatever reason, throws {@link PackgeManagerException}.
+ * <p>
+ * Implementation detail: This method must NOT have any side effects. It would
+ * ideally be static, but, it requires locks to read system state.
+ *
+ * @throws PackageManagerException If the package fails any of the validation checks
+ */
+ private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags)
+ throws PackageManagerException {
+ if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
+ assertCodePolicy(pkg);
+ }
+
+ if (pkg.applicationInfo.getCodePath() == null ||
+ pkg.applicationInfo.getResourcePath() == null) {
+ // Bail out. The resource and code paths haven't been set.
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Code and resource paths haven't been set correctly");
+ }
+
+ // Make sure we're not adding any bogus keyset info
+ KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ ksms.assertScannedPackageValid(pkg);
+
+ synchronized (mPackages) {
+ // The special "android" package can only be defined once
+ if (pkg.packageName.equals("android")) {
+ if (mAndroidApplication != null) {
+ Slog.w(TAG, "*************************************************");
+ Slog.w(TAG, "Core android package being redefined. Skipping.");
+ Slog.w(TAG, " codePath=" + pkg.codePath);
+ Slog.w(TAG, "*************************************************");
+ throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+ "Core android package being redefined. Skipping.");
+ }
}
- final int childCount = pkg.childPackages.size();
- for (int i = 0; i < childCount; i++) {
- PackageParser.Package childPkg = pkg.childPackages.get(i);
- if (mSettings.hasOtherDisabledSystemPkgWithChildLPr(pkg.packageName,
- childPkg.packageName)) {
- throw new PackageManagerException("Cannot override a child package of "
- + "another disabled system app. Ignoring package " + pkg.packageName);
+
+ // A package name must be unique; don't allow duplicates
+ if (mPackages.containsKey(pkg.packageName)
+ || mSharedLibraries.containsKey(pkg.packageName)) {
+ throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+ "Application package " + pkg.packageName
+ + " already installed. Skipping duplicate.");
+ }
+
+ // Only privileged apps and updated privileged apps can add child packages.
+ if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
+ if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
+ throw new PackageManagerException("Only privileged apps can add child "
+ + "packages. Ignoring package " + pkg.packageName);
+ }
+ final int childCount = pkg.childPackages.size();
+ for (int i = 0; i < childCount; i++) {
+ PackageParser.Package childPkg = pkg.childPackages.get(i);
+ if (mSettings.hasOtherDisabledSystemPkgWithChildLPr(pkg.packageName,
+ childPkg.packageName)) {
+ throw new PackageManagerException("Can't override child of "
+ + "another disabled app. Ignoring package " + pkg.packageName);
+ }
+ }
+ }
+
+ // If we're only installing presumed-existing packages, require that the
+ // scanned APK is both already known and at the path previously established
+ // for it. Previously unknown packages we pick up normally, but if we have an
+ // a priori expectation about this package's install presence, enforce it.
+ // With a singular exception for new system packages. When an OTA contains
+ // a new system package, we allow the codepath to change from a system location
+ // to the user-installed location. If we don't allow this change, any newer,
+ // user-installed version of the application will be ignored.
+ if ((policyFlags & SCAN_REQUIRE_KNOWN) != 0) {
+ if (mExpectingBetter.containsKey(pkg.packageName)) {
+ logCriticalInfo(Log.WARN,
+ "Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);
+ } else {
+ PackageSetting known = mSettings.getPackageLPr(pkg.packageName);
+ if (known != null) {
+ if (DEBUG_PACKAGE_SCANNING) {
+ Log.d(TAG, "Examining " + pkg.codePath
+ + " and requiring known paths " + known.codePathString
+ + " & " + known.resourcePathString);
+ }
+ if (!pkg.applicationInfo.getCodePath().equals(known.codePathString)
+ || !pkg.applicationInfo.getResourcePath().equals(
+ known.resourcePathString)) {
+ throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
+ "Application package " + pkg.packageName
+ + " found at " + pkg.applicationInfo.getCodePath()
+ + " but expected at " + known.codePathString
+ + "; ignoring.");
+ }
+ }
+ }
+ }
+
+ // Verify that this new package doesn't have any content providers
+ // that conflict with existing packages. Only do this if the
+ // package isn't already installed, since we don't want to break
+ // things that are installed.
+ if ((policyFlags & SCAN_NEW_INSTALL) != 0) {
+ final int N = pkg.providers.size();
+ int i;
+ for (i=0; i<N; i++) {
+ PackageParser.Provider p = pkg.providers.get(i);
+ if (p.info.authority != null) {
+ String names[] = p.info.authority.split(";");
+ for (int j = 0; j < names.length; j++) {
+ if (mProvidersByAuthority.containsKey(names[j])) {
+ PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
+ final String otherPackageName =
+ ((other != null && other.getComponentName() != null) ?
+ other.getComponentName().getPackageName() : "?");
+ throw new PackageManagerException(
+ INSTALL_FAILED_CONFLICTING_PROVIDER,
+ "Can't install because provider name " + names[j]
+ + " (in package " + pkg.applicationInfo.packageName
+ + ") is already used by " + otherPackageName);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a scanned package to the system. When this method is finished, the package will
+ * be available for query, resolution, etc...
+ */
+ private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,
+ UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {
+ final String pkgName = pkg.packageName;
+ if (mCustomResolverComponentName != null &&
+ mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
+ setUpCustomResolverActivity(pkg);
+ }
+
+ if (pkg.packageName.equals("android")) {
+ synchronized (mPackages) {
+ if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
+ // Set up information for our fall-back user intent resolution activity.
+ mPlatformPackage = pkg;
+ pkg.mVersionCode = mSdkVersion;
+ mAndroidApplication = pkg.applicationInfo;
+
+ if (!mResolverReplaced) {
+ mResolveActivity.applicationInfo = mAndroidApplication;
+ mResolveActivity.name = ResolverActivity.class.getName();
+ mResolveActivity.packageName = mAndroidApplication.packageName;
+ mResolveActivity.processName = "system:ui";
+ mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
+ mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
+ mResolveActivity.exported = true;
+ mResolveActivity.enabled = true;
+ mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_KEYBOARD
+ | ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ mResolveInfo.activityInfo = mResolveActivity;
+ mResolveInfo.priority = 0;
+ mResolveInfo.preferredOrder = 0;
+ mResolveInfo.match = 0;
+ mResolveComponentName = new ComponentName(
+ mAndroidApplication.packageName, mResolveActivity.name);
+ }
}
}
}
+ ArrayList<PackageParser.Package> clientLibPkgs = null;
// writer
synchronized (mPackages) {
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
@@ -8640,10 +8736,6 @@
}
}
- // Make sure we're not adding any bogus keyset info
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
- ksms.assertScannedPackageValid(pkg);
-
// writer
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
@@ -8655,8 +8747,9 @@
// Note that |user| might be null during the initial boot scan. If a codePath
// for an app has changed during a boot scan, it's due to an app update that's
// part of the system partition and marker changes must be applied to all users.
- maybeRenameForeignDexMarkers(pkgSetting.pkg, pkg,
- (user != null) ? user : UserHandle.ALL);
+ final int userId = ((user != null) ? user : UserHandle.ALL).getIdentifier();
+ final int[] userIds = resolveUserIds(userId);
+ maybeRenameForeignDexMarkers(pkgSetting.pkg, pkg, userIds);
}
// Add the new setting to mSettings
@@ -8672,25 +8765,8 @@
}
}
- // Take care of first install / last update times.
- if (currentTime != 0) {
- if (pkgSetting.firstInstallTime == 0) {
- pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
- } else if ((scanFlags&SCAN_UPDATE_TIME) != 0) {
- pkgSetting.lastUpdateTime = currentTime;
- }
- } else if (pkgSetting.firstInstallTime == 0) {
- // We need *something*. Take time time stamp of the file.
- pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
- } else if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
- if (scanFileTime != pkgSetting.timeStamp) {
- // A package on the system image has changed; consider this
- // to be an update.
- pkgSetting.lastUpdateTime = scanFileTime;
- }
- }
-
// Add the package's KeySets to the global KeySetManagerService
+ KeySetManagerService ksms = mSettings.mKeySetManagerService;
ksms.addScannedPackageLPw(pkg);
int N = pkg.providers.size();
@@ -8699,7 +8775,7 @@
for (i=0; i<N; i++) {
PackageParser.Provider p = pkg.providers.get(i);
p.info.processName = fixProcessName(pkg.applicationInfo.processName,
- p.info.processName, pkg.applicationInfo.uid);
+ p.info.processName);
mProviders.addProvider(p);
p.syncable = p.info.isSyncable;
if (p.info.authority != null) {
@@ -8725,7 +8801,7 @@
p.info.authority = p.info.authority + ";" + names[j];
}
if (DEBUG_PACKAGE_SCANNING) {
- if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+ if (chatty)
Log.d(TAG, "Registered content provider: " + names[j]
+ ", className = " + p.info.name + ", isSyncable = "
+ p.info.isSyncable);
@@ -8740,7 +8816,7 @@
}
}
}
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8758,9 +8834,9 @@
for (i=0; i<N; i++) {
PackageParser.Service s = pkg.services.get(i);
s.info.processName = fixProcessName(pkg.applicationInfo.processName,
- s.info.processName, pkg.applicationInfo.uid);
+ s.info.processName);
mServices.addService(s);
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8778,9 +8854,9 @@
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.receivers.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName,
- a.info.processName, pkg.applicationInfo.uid);
+ a.info.processName);
mReceivers.addActivity(a, "receiver");
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8798,9 +8874,9 @@
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.activities.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName,
- a.info.processName, pkg.applicationInfo.uid);
+ a.info.processName);
mActivities.addActivity(a, "activity");
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8822,7 +8898,7 @@
final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
if (cur == null || isPackageUpdate) {
mPermissionGroups.put(pg.info.name, pg);
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8837,7 +8913,7 @@
Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+ pg.info.packageName + " ignored: original from "
+ cur.info.packageName);
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8916,7 +8992,7 @@
bp.uid = pkg.applicationInfo.uid;
bp.sourcePackage = p.info.packageName;
p.info.flags |= PermissionInfo.FLAG_INSTALLED;
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8935,7 +9011,7 @@
+ p.info.packageName + " ignored: original from "
+ bp.sourcePackage);
}
- } else if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ } else if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8965,11 +9041,10 @@
a.info.dataDir = pkg.applicationInfo.dataDir;
a.info.deviceProtectedDataDir = pkg.applicationInfo.deviceProtectedDataDir;
a.info.credentialProtectedDataDir = pkg.applicationInfo.credentialProtectedDataDir;
-
a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir;
a.info.secondaryNativeLibraryDir = pkg.applicationInfo.secondaryNativeLibraryDir;
mInstrumentation.put(a.getComponentName(), a);
- if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -8989,8 +9064,6 @@
}
}
- pkgSetting.setTimeStamp(scanFileTime);
-
// Create idmap files for pairs of (packages, overlay packages).
// Note: "android", ie framework-res.apk, is handled by native layers.
if (pkg.mOverlayTarget != null) {
@@ -9020,11 +9093,10 @@
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"scanPackageLI failed to createIdmap");
}
- return pkg;
}
- private void maybeRenameForeignDexMarkers(PackageParser.Package existing,
- PackageParser.Package update, UserHandle user) {
+ private static void maybeRenameForeignDexMarkers(PackageParser.Package existing,
+ PackageParser.Package update, int[] userIds) {
if (existing.applicationInfo == null || update.applicationInfo == null) {
// This isn't due to an app installation.
return;
@@ -9072,7 +9144,7 @@
markerSuffixes.add(updatedPathName.replace('/', '@'));
}
- for (int userId : resolveUserIds(user.getIdentifier())) {
+ for (int userId : userIds) {
File profileDir = Environment.getDataProfilesDeForeignDexDirectory(userId);
for (String markerSuffix : markerSuffixes) {
@@ -9098,8 +9170,9 @@
*
* If {@code extractLibs} is true, native libraries are extracted from the app if required.
*/
- private void derivePackageAbi(PackageParser.Package pkg, File scanFile,
- String cpuAbiOverride, boolean extractLibs)
+ private static void derivePackageAbi(PackageParser.Package pkg, File scanFile,
+ String cpuAbiOverride, boolean extractLibs,
+ File appLib32InstallDir)
throws PackageManagerException {
// TODO: We can probably be smarter about this stuff. For installed apps,
// we can calculate this information at install time once and for all. For
@@ -9108,7 +9181,7 @@
// Give ourselves some initial paths; we'll come back for another
// pass once we've determined ABI below.
- setNativeLibraryPaths(pkg);
+ setNativeLibraryPaths(pkg, appLib32InstallDir);
// We would never need to extract libs for forward-locked and external packages,
// since the container service will do it for us. We shouldn't attempt to
@@ -9243,7 +9316,7 @@
// Now that we've calculated the ABIs and determined if it's an internal app,
// we will go ahead and populate the nativeLibraryPath.
- setNativeLibraryPaths(pkg);
+ setNativeLibraryPaths(pkg, appLib32InstallDir);
}
/**
@@ -9260,7 +9333,7 @@
* adds unnecessary complexity.
*/
private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
- PackageParser.Package scannedPackage, boolean bootComplete) {
+ PackageParser.Package scannedPackage) {
String requiredInstructionSet = null;
if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
requiredInstructionSet = VMRuntime.getInstructionSet(
@@ -9425,7 +9498,7 @@
* Derive and set the location of native libraries for the given package,
* which varies depending on where and how the package was installed.
*/
- private void setNativeLibraryPaths(PackageParser.Package pkg) {
+ private static void setNativeLibraryPaths(PackageParser.Package pkg, File appLib32InstallDir) {
final ApplicationInfo info = pkg.applicationInfo;
final String codePath = pkg.codePath;
final File codeFile = new File(codePath);
@@ -9464,7 +9537,7 @@
.getAbsolutePath();
} else {
final String apkName = deriveCodePathName(codePath);
- info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
+ info.nativeLibraryRootDir = new File(appLib32InstallDir, apkName)
.getAbsolutePath();
}
@@ -9492,7 +9565,7 @@
* of this information, and instead assume that the system was built
* sensibly.
*/
- private void setBundledAppAbisAndRoots(PackageParser.Package pkg,
+ private static void setBundledAppAbisAndRoots(PackageParser.Package pkg,
PackageSetting pkgSetting) {
final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath());
@@ -14428,7 +14501,7 @@
childRemovedRes.isUpdate = false;
childRemovedRes.dataRemoved = true;
synchronized (mPackages) {
- PackageSetting childPs = mSettings.peekPackageLPr(childPkg.packageName);
+ PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
if (childPs != null) {
childRemovedRes.origUsers = childPs.queryInstalledUsers(allUsers, true);
}
@@ -14583,7 +14656,7 @@
}
} else {
synchronized (mPackages) {
- PackageSetting ps = mSettings.peekPackageLPr(pkg.packageName);
+ PackageSetting ps = mSettings.getPackageLPr(pkg.packageName);
if (ps != null) {
res.removedInfo.removedForAllUsers = mPackages.get(ps.name) == null;
if (res.removedInfo.removedChildPackages != null) {
@@ -14761,7 +14834,7 @@
for (int i = 0; i < childCount; i++) {
PackageSetting childPs = null;
synchronized (mPackages) {
- childPs = mSettings.peekPackageLPr(ps.childPackageNames.get(i));
+ childPs = mSettings.getPackageLPr(ps.childPackageNames.get(i));
}
if (childPs != null) {
NativeLibraryHelper.removeNativeBinariesLI(childPs
@@ -15034,7 +15107,7 @@
childRes.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
childRes.pkg = childPkg;
childRes.name = childPkg.packageName;
- PackageSetting childPs = mSettings.peekPackageLPr(childPkg.packageName);
+ PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
if (childPs != null) {
childRes.origUsers = childPs.queryInstalledUsers(
sUserManager.getUserIds(), true);
@@ -15256,7 +15329,7 @@
String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
args.abiOverride : pkg.cpuAbiOverride);
derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
- true /* extract libs */);
+ true /*extractLibs*/, mAppLib32InstallDir);
} catch (PackageManagerException pme) {
Slog.e(TAG, "Error deriving application ABI", pme);
res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
@@ -15266,7 +15339,7 @@
// Shared libraries for the package need to be updated.
synchronized (mPackages) {
try {
- updateSharedLibrariesLPw(pkg, null);
+ updateSharedLibrariesLPr(pkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateSharedLibrariesLPw failed: " + e.getMessage());
}
@@ -15315,7 +15388,7 @@
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName);
- PackageSetting childPs = mSettings.peekPackageLPr(childPkg.packageName);
+ PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
if (childPs != null) {
childRes.newUsers = childPs.queryInstalledUsers(
sUserManager.getUserIds(), true);
@@ -16114,7 +16187,7 @@
}
try {
// update shared libraries for the newly re-installed system package
- updateSharedLibrariesLPw(newPkg, null);
+ updateSharedLibrariesLPr(newPkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
@@ -16192,7 +16265,7 @@
for (int i = 0; i < childCount; i++) {
PackageSetting childPs;
synchronized (mPackages) {
- childPs = mSettings.peekPackageLPr(ps.childPackageNames.get(i));
+ childPs = mSettings.getPackageLPr(ps.childPackageNames.get(i));
}
if (childPs != null) {
PackageRemovedInfo childOutInfo = (outInfo != null
@@ -16369,7 +16442,7 @@
PackageRemovedInfo childInfo = new PackageRemovedInfo();
childInfo.removedPackage = childPackageName;
outInfo.removedChildPackages.put(childPackageName, childInfo);
- PackageSetting childPs = mSettings.peekPackageLPr(childPackageName);
+ PackageSetting childPs = mSettings.getPackageLPr(childPackageName);
if (childPs != null) {
childInfo.origUsers = childPs.queryInstalledUsers(allUserHandles, true);
}
@@ -16409,14 +16482,14 @@
// app but were not declared in the update.
if (isSystemApp(ps)) {
synchronized (mPackages) {
- PackageSetting updatedPs = mSettings.peekPackageLPr(ps.name);
+ PackageSetting updatedPs = mSettings.getPackageLPr(ps.name);
final int childCount = (updatedPs.childPackageNames != null)
? updatedPs.childPackageNames.size() : 0;
for (int i = 0; i < childCount; i++) {
String childPackageName = updatedPs.childPackageNames.get(i);
if (outInfo.removedChildPackages == null
|| outInfo.removedChildPackages.indexOfKey(childPackageName) < 0) {
- PackageSetting childPs = mSettings.peekPackageLPr(childPackageName);
+ PackageSetting childPs = mSettings.getPackageLPr(childPackageName);
if (childPs == null) {
continue;
}
@@ -18397,9 +18470,10 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
(new PackageManagerShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@Override
@@ -20604,9 +20678,9 @@
}
/** Called by UserManagerService */
- void createNewUser(int userId) {
+ void createNewUser(int userId, String[] disallowedPackages) {
synchronized (mInstallLock) {
- mSettings.createNewUserLI(this, mInstaller, userId);
+ mSettings.createNewUserLI(this, mInstaller, userId, disallowedPackages);
}
synchronized (mPackages) {
scheduleWritePackageRestrictionsLocked(userId);
@@ -21127,6 +21201,20 @@
return mSettings.wasPackageEverLaunchedLPr(packageName, userId);
}
}
+
+ @Override
+ public void grantRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy) {
+ PackageManagerService.this.grantRuntimePermission(packageName, name, userId,
+ overridePolicy);
+ }
+
+ @Override
+ public void revokeRuntimePermission(String packageName, String name, int userId,
+ boolean overridePolicy) {
+ PackageManagerService.this.revokeRuntimePermission(packageName, name, userId,
+ overridePolicy);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 61374f3..834d343 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -30,6 +30,10 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.ApkLite;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -48,6 +52,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.PrintWriterPrinter;
+import com.android.internal.content.PackageHelper;
import com.android.internal.util.SizedInputStream;
import dalvik.system.DexFile;
@@ -70,6 +75,11 @@
import java.util.concurrent.TimeUnit;
class PackageManagerShellCommand extends ShellCommand {
+ /** Path for streaming APK content */
+ private static final String STDIN_PATH = "-";
+ /** Whether or not APK content must be streamed from stdin */
+ private static final boolean FORCE_STREAM_INSTALL = true;
+
final IPackageManager mInterface;
final private WeakHashMap<String, Resources> mResourceCache =
new WeakHashMap<String, Resources>();
@@ -134,15 +144,45 @@
return -1;
}
+ private void setParamsSize(InstallParams params, String inPath) {
+ // If we're forced to stream the package, the params size
+ // must be set via command-line argument. There's nothing
+ // to do here.
+ if (FORCE_STREAM_INSTALL) {
+ return;
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
+ File file = new File(inPath);
+ if (file.isFile()) {
+ try {
+ ApkLite baseApk = PackageParser.parseApkLite(file, 0);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+ params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
+ pkgLite, false, params.sessionParams.abiOverride));
+ } catch (PackageParserException | IOException e) {
+ pw.println("Error: Failed to parse APK file: " + file);
+ throw new IllegalArgumentException(
+ "Error: Failed to parse APK file: " + file, e);
+ }
+ } else {
+ pw.println("Error: Can't open non-file: " + inPath);
+ throw new IllegalArgumentException("Error: Can't open non-file: " + inPath);
+ }
+ }
+ }
+
private int runInstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final InstallParams params = makeInstallParams();
+ final String inPath = getNextArg();
+
+ setParamsSize(params, inPath);
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
- final String inPath = getNextArg();
- if (inPath == null && params.sessionParams.sizeBytes == 0) {
+ if (inPath == null && params.sessionParams.sizeBytes == -1) {
pw.println("Error: must either specify a package size or an APK file");
return 1;
}
@@ -1055,7 +1095,11 @@
}
break;
case "-S":
- sessionParams.setSize(Long.parseLong(getNextArg()));
+ final long sizeBytes = Long.parseLong(getNextArg());
+ if (sizeBytes <= 0) {
+ throw new IllegalArgumentException("Size must be positive");
+ }
+ sessionParams.setSize(sizeBytes);
break;
case "--abi":
sessionParams.abiOverride = checkAbiArgument(getNextArg());
@@ -1160,7 +1204,11 @@
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
- if ("-".equals(inPath)) {
+ if (FORCE_STREAM_INSTALL && inPath != null && !STDIN_PATH.equals(inPath)) {
+ pw.println("Error: APK content must be streamed");
+ return 1;
+ }
+ if (STDIN_PATH.equals(inPath)) {
inPath = null;
} else if (inPath != null) {
final File file = new File(inPath);
@@ -1168,6 +1216,10 @@
sizeBytes = file.length();
}
}
+ if (sizeBytes <= 0) {
+ pw.println("Error: must specify a APK size");
+ return 1;
+ }
final SessionInfo info = mInterface.getPackageInstaller().getSessionInfo(sessionId);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 8cab355..6d514e4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -430,10 +430,6 @@
}
PackageSetting getPackageLPr(String pkgName) {
- return peekPackageLPr(pkgName);
- }
-
- PackageSetting peekPackageLPr(String pkgName) {
return mPackages.get(pkgName);
}
@@ -502,23 +498,20 @@
}
/** Gets and optionally creates a new shared user id. */
- SharedUserSetting getSharedUserLPw(String name,
- int pkgFlags, int pkgPrivateFlags, boolean create) {
+ SharedUserSetting getSharedUserLPw(String name, int pkgFlags, int pkgPrivateFlags,
+ boolean create) throws PackageManagerException {
SharedUserSetting s = mSharedUsers.get(name);
- if (s == null) {
- if (!create) {
- return null;
- }
+ if (s == null && create) {
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = newUserIdLPw(s);
- Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId);
- // < 0 means we couldn't assign a userid; fall out and return
- // s, which is currently null
- if (s.userId >= 0) {
- mSharedUsers.put(name, s);
+ if (s.userId < 0) {
+ // < 0 means we couldn't assign a userid; throw exception
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Creating shared user " + name + " failed");
}
+ Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId);
+ mSharedUsers.put(name, s);
}
-
return s;
}
@@ -875,9 +868,9 @@
* Writes per-user package restrictions if the user state has changed. If the user
* state has not changed, this does nothing.
*/
- void writeUserRestrictions(PackageSetting newPackage, PackageSetting oldPackage) {
+ void writeUserRestrictionsLPw(PackageSetting newPackage, PackageSetting oldPackage) {
// package doesn't exist; do nothing
- if (peekPackageLPr(newPackage.name) == null) {
+ if (getPackageLPr(newPackage.name) == null) {
return;
}
// no users defined; do nothing
@@ -3956,7 +3949,7 @@
}
void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
- int userHandle) {
+ int userHandle, String[] disallowedPackages) {
String[] volumeUuids;
String[] names;
int[] appIds;
@@ -3977,8 +3970,10 @@
if (ps.pkg == null || ps.pkg.applicationInfo == null) {
continue;
}
+ final boolean shouldInstall = ps.isSystem() &&
+ !ArrayUtils.contains(disallowedPackages, ps.name);
// Only system apps are initially installed.
- ps.setInstalled(ps.isSystem(), userHandle);
+ ps.setInstalled(shouldInstall, userHandle);
// Need to create a data directory for all apps under this user. Accumulate all
// required args and call the installer after mPackages lock has been released
volumeUuids[i] = ps.volumeUuid;
@@ -4088,7 +4083,7 @@
return mVerifierDeviceIdentity;
}
- public boolean hasOtherDisabledSystemPkgWithChildLPr(String parentPackageName,
+ boolean hasOtherDisabledSystemPkgWithChildLPr(String parentPackageName,
String childPackageName) {
final int packageCount = mDisabledSysPackages.size();
for (int i = 0; i < packageCount; i++) {
@@ -4260,7 +4255,7 @@
ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP",
};
- static final Object[] PRIVATE_FLAG_DUMP_SPEC = new Object[] {
+ private static final Object[] PRIVATE_FLAG_DUMP_SPEC = new Object[] {
ApplicationInfo.PRIVATE_FLAG_HIDDEN, "HIDDEN",
ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK, "FORWARD_LOCK",
@@ -4272,7 +4267,8 @@
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE, "PARTIALLY_DIRECT_BOOT_AWARE",
ApplicationInfo.PRIVATE_FLAG_EPHEMERAL, "EPHEMERAL",
ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER, "REQUIRED_FOR_SYSTEM_USER",
- ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES, "RESIZEABLE_ACTIVITIES",
+ ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET, "RESIZEABLE_ACTIVITIES_EXPLICITLY_SET",
+ ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION, "RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION",
ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND, "BACKUP_IN_FOREGROUND",
};
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 13f558e..19bf751 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -67,6 +67,7 @@
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -3366,13 +3367,14 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
enforceShell();
final long token = injectClearCallingIdentity();
try {
- final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
+ final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback,
+ resultReceiver);
resultReceiver.send(status, null);
} finally {
injectRestoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c1cb032..efd6f46 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
@@ -96,6 +97,7 @@
import com.android.server.SystemService;
import com.android.server.am.UserState;
import com.android.server.storage.DeviceStorageMonitorInternal;
+
import libcore.io.IoUtils;
import libcore.util.Objects;
@@ -590,7 +592,7 @@
@Override
public int[] getProfileIds(int userId, boolean enabledOnly) {
if (userId != UserHandle.getCallingUserId()) {
- checkManageUsersPermission("getting profiles related to user " + userId);
+ checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
}
final long ident = Binder.clearCallingIdentity();
try {
@@ -671,12 +673,10 @@
public boolean isSameProfileGroup(int userId, int otherUserId) {
if (userId == otherUserId) return true;
checkManageUsersPermission("check if in the same profile group");
- synchronized (mPackagesLock) {
- return isSameProfileGroupLP(userId, otherUserId);
- }
+ return isSameProfileGroupNoChecks(userId, otherUserId);
}
- private boolean isSameProfileGroupLP(int userId, int otherUserId) {
+ private boolean isSameProfileGroupNoChecks(int userId, int otherUserId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
@@ -878,12 +878,10 @@
public boolean isManagedProfile(int userId) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
- synchronized (mPackagesLock) {
- if (!isSameProfileGroupLP(callingUserId, userId)) {
- throw new SecurityException(
- "You need MANAGE_USERS permission to: check if specified user a " +
- "managed profile outside your profile group");
- }
+ if (!isSameProfileGroupNoChecks(callingUserId, userId)) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to: check if specified user a " +
+ "managed profile outside your profile group");
}
}
synchronized (mUsersLock) {
@@ -893,6 +891,18 @@
}
@Override
+ public boolean isUserUnlockingOrUnlocked(int userId) {
+ int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId != userId && !hasManageUsersPermission()) {
+ if (!isSameProfileGroupNoChecks(callingUserId, userId)) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to: check isUserUnlockingOrUnlocked");
+ }
+ }
+ return mLocalService.isUserUnlockingOrUnlocked(userId);
+ }
+
+ @Override
public boolean isDemoUser(int userId) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
@@ -1705,14 +1715,13 @@
UserRestrictionsUtils
.readRestrictions(parser, mGuestRestrictions);
}
- } else if (parser.getName().equals(TAG_DEVICE_POLICY_RESTRICTIONS)
- ) {
- UserRestrictionsUtils.readRestrictions(parser,
- newDevicePolicyGlobalUserRestrictions);
}
break;
}
}
+ } else if (name.equals(TAG_DEVICE_POLICY_RESTRICTIONS)) {
+ UserRestrictionsUtils.readRestrictions(parser,
+ newDevicePolicyGlobalUserRestrictions);
} else if (name.equals(TAG_GLOBAL_RESTRICTION_OWNER_ID)) {
String ownerUserId = parser.getAttributeValue(null, ATTR_ID);
if (ownerUserId != null) {
@@ -2182,9 +2191,17 @@
}
@Override
- public UserInfo createProfileForUser(String name, int flags, int userId) {
+ public UserInfo createProfileForUser(String name, int flags, int userId,
+ String[] disallowedPackages) {
checkManageOrCreateUsersPermission(flags);
- return createUserInternal(name, flags, userId);
+ return createUserInternal(name, flags, userId, disallowedPackages);
+ }
+
+ @Override
+ public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, int userId,
+ String[] disallowedPackages) {
+ checkManageOrCreateUsersPermission(flags);
+ return createUserInternalUnchecked(name, flags, userId, disallowedPackages);
}
@Override
@@ -2194,20 +2211,26 @@
}
private UserInfo createUserInternal(String name, int flags, int parentId) {
+ return createUserInternal(name, flags, parentId, null);
+ }
+
+ private UserInfo createUserInternal(String name, int flags, int parentId,
+ String[] disallowedPackages) {
if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
return null;
}
+ return createUserInternalUnchecked(name, flags, parentId, disallowedPackages);
+ }
+
+ private UserInfo createUserInternalUnchecked(String name, int flags, int parentId,
+ String[] disallowedPackages) {
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm.isMemoryLow()) {
Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
return null;
}
- return createUserInternalUnchecked(name, flags, parentId);
- }
-
- private UserInfo createUserInternalUnchecked(String name, int flags, int parentId) {
if (ActivityManager.isLowRamDeviceStatic()) {
return null;
}
@@ -2321,7 +2344,7 @@
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
mPm.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
- mPm.createNewUser(userId);
+ mPm.createNewUser(userId, disallowedPackages);
userInfo.partial = false;
synchronized (mPackagesLock) {
writeUserLP(userData);
@@ -2371,7 +2394,8 @@
@Override
public UserInfo createRestrictedProfile(String name, int parentUserId) {
checkManageOrCreateUsersPermission("setupRestrictedProfile");
- final UserInfo user = createProfileForUser(name, UserInfo.FLAG_RESTRICTED, parentUserId);
+ final UserInfo user = createProfileForUser(
+ name, UserInfo.FLAG_RESTRICTED, parentUserId, null);
if (user == null) {
return null;
}
@@ -3209,8 +3233,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
int onShellCommand(Shell shell, String cmd) {
@@ -3532,7 +3557,7 @@
@Override
public UserInfo createUserEvenWhenDisallowed(String name, int flags) {
- UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL);
+ UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL, null);
// Keep this in sync with UserManager.createUser
if (user != null && !user.isAdmin()) {
setUserRestriction(UserManager.DISALLOW_SMS, true, user.id);
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
index e6ec6a6..92729dc 100644
--- a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -43,7 +43,10 @@
// Default value when max burnin radius is not set.
public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1;
- private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
+ private static final long BURNIN_PROTECTION_FIRST_WAKEUP_INTERVAL_MS =
+ TimeUnit.MINUTES.toMillis(1);
+ private static final long BURNIN_PROTECTION_SUBSEQUENT_WAKEUP_INTERVAL_MS =
+ TimeUnit.MINUTES.toMillis(2);
private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10);
private static final boolean DEBUG = false;
@@ -138,6 +141,9 @@
// We don't want to adjust offsets immediately after the device goes into ambient mode.
// Instead, we want to wait until it's more likely that the user is not observing the
// screen anymore.
+ final long interval = mFirstUpdate
+ ? BURNIN_PROTECTION_FIRST_WAKEUP_INTERVAL_MS
+ : BURNIN_PROTECTION_SUBSEQUENT_WAKEUP_INTERVAL_MS;
if (mFirstUpdate) {
mFirstUpdate = false;
} else {
@@ -156,8 +162,7 @@
// Next adjustment at least ten seconds in the future.
long nextWall = nowWall + BURNIN_PROTECTION_MINIMAL_INTERVAL_MS;
// And aligned to the minute.
- nextWall = nextWall - nextWall % BURNIN_PROTECTION_WAKEUP_INTERVAL_MS
- + BURNIN_PROTECTION_WAKEUP_INTERVAL_MS;
+ nextWall = (nextWall - (nextWall % interval)) + interval;
// Use elapsed real time that is adjusted to full minute on wall clock.
final long nextElapsed = nowElapsed + (nextWall - nowWall);
if (DEBUG) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b555938..6e5c025 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -28,6 +28,7 @@
import static android.view.WindowManager.DOCKED_TOP;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static android.view.WindowManager.LayoutParams.*;
@@ -72,6 +73,7 @@
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
import android.hardware.input.InputManagerInternal;
+import android.hardware.power.V1_0.PowerHint;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -140,6 +142,7 @@
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.internal.policy.IShortcutService;
@@ -176,12 +179,13 @@
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_WAKEUP = false;
static final boolean SHOW_STARTING_ANIMATIONS = true;
- static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
// No longer recommended for desk docks;
static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false;
+ static final boolean ALTERNATE_CAR_MODE_NAV_SIZE = false;
+
static final int SHORT_PRESS_POWER_NOTHING = 0;
static final int SHORT_PRESS_POWER_GO_TO_SLEEP = 1;
static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2;
@@ -595,6 +599,13 @@
private static final int DISMISS_KEYGUARD_CONTINUE = 2; // Keyguard has been dismissed.
int mDismissKeyguard = DISMISS_KEYGUARD_NONE;
+ /**
+ * Indicates that we asked the Keyguard to be dismissed and we just wait for the Keyguard to
+ * dismiss itself.
+ */
+ @GuardedBy("Lw")
+ private boolean mCurrentlyDismissingKeyguard;
+
/** The window that is currently dismissing the keyguard. Dismissing the keyguard must only
* be done once per window. */
private WindowState mWinDismissingKeyguard;
@@ -898,7 +909,7 @@
@Override
public void run() {
// send interaction hint to improve redraw performance
- mPowerManagerInternal.powerHint(PowerManagerInternal.POWER_HINT_INTERACTION, 0);
+ mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
updateRotation(false);
}
};
@@ -1373,8 +1384,13 @@
case LONG_PRESS_BACK_NOTHING:
break;
case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
- Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
- startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
break;
}
}
@@ -1824,7 +1840,7 @@
public void onFling(int duration) {
if (mPowerManagerInternal != null) {
mPowerManagerInternal.powerHint(
- PowerManagerInternal.POWER_HINT_INTERACTION, duration);
+ PowerHint.INTERACTION, duration);
}
}
@Override
@@ -2451,22 +2467,24 @@
mNavigationBarWidthForRotationDefault[mSeascapeRotation] =
res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
- // Height of the navigation bar when presented horizontally at bottom
- mNavigationBarHeightForRotationInCarMode[mPortraitRotation] =
- mNavigationBarHeightForRotationInCarMode[mUpsideDownRotation] =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height_car_mode);
- mNavigationBarHeightForRotationInCarMode[mLandscapeRotation] =
- mNavigationBarHeightForRotationInCarMode[mSeascapeRotation] = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height_landscape_car_mode);
+ if (ALTERNATE_CAR_MODE_NAV_SIZE) {
+ // Height of the navigation bar when presented horizontally at bottom
+ mNavigationBarHeightForRotationInCarMode[mPortraitRotation] =
+ mNavigationBarHeightForRotationInCarMode[mUpsideDownRotation] =
+ res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_car_mode);
+ mNavigationBarHeightForRotationInCarMode[mLandscapeRotation] =
+ mNavigationBarHeightForRotationInCarMode[mSeascapeRotation] = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape_car_mode);
- // Width of the navigation bar when presented vertically along one side
- mNavigationBarWidthForRotationInCarMode[mPortraitRotation] =
- mNavigationBarWidthForRotationInCarMode[mUpsideDownRotation] =
- mNavigationBarWidthForRotationInCarMode[mLandscapeRotation] =
- mNavigationBarWidthForRotationInCarMode[mSeascapeRotation] =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_width_car_mode);
+ // Width of the navigation bar when presented vertically along one side
+ mNavigationBarWidthForRotationInCarMode[mPortraitRotation] =
+ mNavigationBarWidthForRotationInCarMode[mUpsideDownRotation] =
+ mNavigationBarWidthForRotationInCarMode[mLandscapeRotation] =
+ mNavigationBarWidthForRotationInCarMode[mSeascapeRotation] =
+ res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_width_car_mode);
+ }
}
/** {@inheritDoc} */
@@ -2598,7 +2616,7 @@
}
private int getNavigationBarWidth(int rotation, int uiMode) {
- if ((uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+ if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
return mNavigationBarWidthForRotationInCarMode[rotation];
} else {
return mNavigationBarWidthForRotationDefault[rotation];
@@ -2619,7 +2637,7 @@
}
private int getNavigationBarHeight(int rotation, int uiMode) {
- if ((uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+ if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
return mNavigationBarHeightForRotationInCarMode[rotation];
} else {
return mNavigationBarHeightForRotationDefault[rotation];
@@ -2717,7 +2735,7 @@
}
}
- if (overrideConfig != null && overrideConfig != EMPTY) {
+ if (overrideConfig != null && !overrideConfig.equals(EMPTY)) {
if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow: creating context based"
+ " on overrideConfig" + overrideConfig + " for starting window");
final Context overrideContext = context.createConfigurationContext(overrideConfig);
@@ -3260,21 +3278,6 @@
mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
null, null, null, 0, null, null);
return -1;
- } else if (SHOW_PROCESSES_ON_ALT_MENU &&
- (metaState & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) {
- Intent service = new Intent();
- service.setClassName(mContext, "com.android.server.LoadAverageService");
- ContentResolver res = mContext.getContentResolver();
- boolean shown = Settings.Global.getInt(
- res, Settings.Global.SHOW_PROCESSES, 0) != 0;
- if (!shown) {
- mContext.startService(service);
- } else {
- mContext.stopService(service);
- }
- Settings.Global.putInt(
- res, Settings.Global.SHOW_PROCESSES, shown ? 0 : 1);
- return -1;
}
}
} else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
@@ -3701,10 +3704,11 @@
@Override
public boolean canShowDismissingWindowWhileLockedLw() {
- // If the keyguard is trusted, it will unlock without a challange. Therefore, windows with
- // FLAG_DISMISS_KEYGUARD don't need to be force hidden, as they will unlock the phone right
- // away anyways.
- return mKeyguardDelegate != null && mKeyguardDelegate.isTrusted();
+ // If the keyguard is trusted, it will unlock without a challenge. Therefore, if we are in
+ // the process of dismissing Keyguard, we don't need to hide them as the phone will be
+ // unlocked right away in any case.
+ return mKeyguardDelegate != null && mKeyguardDelegate.isTrusted()
+ && mCurrentlyDismissingKeyguard;
}
private void launchAssistLongPressAction() {
@@ -3957,14 +3961,6 @@
}
}
}
- final InputEventReceiver.Factory mHideNavInputEventReceiverFactory =
- new InputEventReceiver.Factory() {
- @Override
- public InputEventReceiver createInputEventReceiver(
- InputChannel inputChannel, Looper looper) {
- return new HideNavInputEventReceiver(inputChannel, looper);
- }
- };
@Override
public void setRecentsVisibilityLw(boolean visible) {
@@ -4189,8 +4185,9 @@
mInputConsumer = null;
}
} else if (mInputConsumer == null) {
- mInputConsumer = mWindowManagerFuncs.addInputConsumer(mHandler.getLooper(),
- mHideNavInputEventReceiverFactory);
+ mInputConsumer = mWindowManagerFuncs.createInputConsumer(mHandler.getLooper(),
+ INPUT_CONSUMER_NAVIGATION,
+ (channel, looper) -> new HideNavInputEventReceiver(channel, looper));
}
// For purposes of positioning and showing the nav bar, if we have
@@ -5411,22 +5408,30 @@
}
} else if (mDismissKeyguard != DISMISS_KEYGUARD_NONE) {
mKeyguardHidden = false;
+ boolean dismissKeyguard = false;
final boolean trusted = mKeyguardDelegate.isTrusted();
- if (trusted) {
- // No need to un-occlude keyguard - we'll dimiss it right away anyways.
- } else if (setKeyguardOccludedLw(false)) {
- changes |= FINISH_LAYOUT_REDO_LAYOUT
- | FINISH_LAYOUT_REDO_CONFIG
- | FINISH_LAYOUT_REDO_WALLPAPER;
- }
if (mDismissKeyguard == DISMISS_KEYGUARD_START) {
+ final boolean willDismiss = trusted && mKeyguardOccluded
+ && mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
+ if (willDismiss) {
+ mCurrentlyDismissingKeyguard = true;
+ }
+ dismissKeyguard = true;
+ }
+
+ // If we are currently dismissing Keyguard, there is no need to unocclude it.
+ if (!mCurrentlyDismissingKeyguard) {
+ if (setKeyguardOccludedLw(false)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ }
+
+ if (dismissKeyguard) {
// Only launch the next keyguard unlock window once per window.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mKeyguardDelegate.dismiss(trusted /* allowWhileOccluded */);
- }
- });
+ mHandler.post(() -> mKeyguardDelegate.dismiss(
+ trusted /* allowWhileOccluded */));
}
} else {
mWinDismissingKeyguard = null;
@@ -5476,11 +5481,23 @@
mStatusBar.getAttrs().privateFlags &= ~PRIVATE_FLAG_KEYGUARD;
mStatusBar.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
return true;
+ } else if (wasOccluded != isOccluded) {
+ mKeyguardOccluded = isOccluded;
+ mKeyguardDelegate.setOccluded(isOccluded, false /* animate */);
+ return false;
} else {
return false;
}
}
+ private void onKeyguardShowingStateChanged(boolean showing) {
+ if (!showing) {
+ synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
+ mCurrentlyDismissingKeyguard = false;
+ }
+ }
+ }
+
private boolean isStatusBarKeyguard() {
return mStatusBar != null
&& (mStatusBar.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
@@ -7011,7 +7028,8 @@
/** {@inheritDoc} */
@Override
public void systemReady() {
- mKeyguardDelegate = new KeyguardServiceDelegate(mContext);
+ mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
+ this::onKeyguardShowingStateChanged);
mKeyguardDelegate.onSystemReady();
readCameraLensCoverState();
@@ -8097,6 +8115,7 @@
pw.print(" mForceStatusBarFromKeyguard=");
pw.println(mForceStatusBarFromKeyguard);
pw.print(prefix); pw.print("mDismissKeyguard="); pw.print(mDismissKeyguard);
+ pw.print(" mCurrentlyDismissingKeyguard="); pw.println(mCurrentlyDismissingKeyguard);
pw.print(" mWinDismissingKeyguard="); pw.print(mWinDismissingKeyguard);
pw.print(" mHomePressed="); pw.println(mHomePressed);
pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.print(mAllowLockscreenWhenOn);
diff --git a/services/core/java/com/android/server/policy/RecentApplicationsBackground.java b/services/core/java/com/android/server/policy/RecentApplicationsBackground.java
deleted file mode 100644
index 694a110..0000000
--- a/services/core/java/com/android/server/policy/RecentApplicationsBackground.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2010 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.policy;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.LinearLayout;
-
-/**
- * A vertical linear layout. However, instead of drawing the background
- * behnd the items, it draws the background outside the items based on the
- * padding. If there isn't enough room to draw both, it clips the background
- * instead of the contents.
- */
-public class RecentApplicationsBackground extends LinearLayout {
- private static final String TAG = "RecentApplicationsBackground";
-
- private boolean mBackgroundSizeChanged;
- private Drawable mBackground;
- private Rect mTmp0 = new Rect();
- private Rect mTmp1 = new Rect();
-
- public RecentApplicationsBackground(Context context) {
- this(context, null);
- init();
- }
-
- public RecentApplicationsBackground(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- private void init() {
- mBackground = getBackground();
- setBackgroundDrawable(null);
- setPadding(0, 0, 0, 0);
- setGravity(Gravity.CENTER);
- }
-
- @Override
- protected boolean setFrame(int left, int top, int right, int bottom) {
- setWillNotDraw(false);
- if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
- mBackgroundSizeChanged = true;
- }
- return super.setFrame(left, top, right, bottom);
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- return who == mBackground || super.verifyDrawable(who);
- }
-
- @Override
- public void jumpDrawablesToCurrentState() {
- super.jumpDrawablesToCurrentState();
- if (mBackground != null) mBackground.jumpToCurrentState();
- }
-
- @Override
- protected void drawableStateChanged() {
- Drawable d = mBackground;
- if (d != null && d.isStateful()) {
- d.setState(getDrawableState());
- }
- super.drawableStateChanged();
- }
-
- @Override
- public void draw(Canvas canvas) {
- final Drawable background = mBackground;
- if (background != null) {
- if (mBackgroundSizeChanged) {
- mBackgroundSizeChanged = false;
- Rect chld = mTmp0;
- Rect bkg = mTmp1;
- mBackground.getPadding(bkg);
- getChildBounds(chld);
- // This doesn't clamp to this view's bounds, which is what we want,
- // so that the drawing is clipped.
- final int top = chld.top - bkg.top;
- final int bottom = chld.bottom + bkg.bottom;
- // The background here is a gradient that wants to
- // extend the full width of the screen (whatever that
- // may be).
- int left, right;
- if (false) {
- // This limits the width of the drawable.
- left = chld.left - bkg.left;
- right = chld.right + bkg.right;
- } else {
- // This expands it to full width.
- left = 0;
- right = getRight();
- }
- background.setBounds(left, top, right, bottom);
- }
- }
- mBackground.draw(canvas);
-
- if (false) {
- android.graphics.Paint p = new android.graphics.Paint();
- p.setColor(0x88ffff00);
- canvas.drawRect(background.getBounds(), p);
- }
- canvas.drawARGB((int)(0.75*0xff), 0, 0, 0);
-
- super.draw(canvas);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mBackground.setCallback(this);
- setWillNotDraw(false);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mBackground.setCallback(null);
- }
-
- private void getChildBounds(Rect r) {
- r.left = r.top = Integer.MAX_VALUE;
- r.bottom = r.right = Integer.MIN_VALUE;
- final int N = getChildCount();
- for (int i=0; i<N; i++) {
- View v = getChildAt(i);
- if (v.getVisibility() == View.VISIBLE) {
- r.left = Math.min(r.left, v.getLeft());
- r.top = Math.min(r.top, v.getTop());
- r.right = Math.max(r.right, v.getRight());
- r.bottom = Math.max(r.bottom, v.getBottom());
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 2069f24..10c237f 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -23,6 +23,7 @@
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.server.UiThread;
+import com.android.server.policy.keyguard.KeyguardStateMonitor.OnShowingStateChangedCallback;
import java.io.PrintWriter;
@@ -49,6 +50,7 @@
private final Handler mScrimHandler;
private final KeyguardState mKeyguardState = new KeyguardState();
private DrawnListener mDrawnListenerWhenConnect;
+ private final OnShowingStateChangedCallback mShowingStateChangedCallback;
private static final class KeyguardState {
KeyguardState() {
@@ -117,9 +119,11 @@
}
};
- public KeyguardServiceDelegate(Context context) {
+ public KeyguardServiceDelegate(Context context,
+ OnShowingStateChangedCallback showingStateChangedCallback) {
mContext = context;
mScrimHandler = UiThread.getHandler();
+ mShowingStateChangedCallback = showingStateChangedCallback;
mScrim = createScrim(context, mScrimHandler);
}
@@ -155,7 +159,7 @@
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) Log.v(TAG, "*** Keyguard connected (yay!)");
mKeyguardService = new KeyguardServiceWrapper(mContext,
- IKeyguardService.Stub.asInterface(service));
+ IKeyguardService.Stub.asInterface(service), mShowingStateChangedCallback);
if (mKeyguardState.systemIsReady) {
// If the system is ready, it means keyguard crashed and restarted.
mKeyguardService.onSystemReady();
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 1d85f34..acc67db 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -26,6 +26,7 @@
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
+import com.android.server.policy.keyguard.KeyguardStateMonitor.OnShowingStateChangedCallback;
import java.io.PrintWriter;
@@ -39,9 +40,11 @@
private IKeyguardService mService;
private String TAG = "KeyguardServiceWrapper";
- public KeyguardServiceWrapper(Context context, IKeyguardService service) {
+ public KeyguardServiceWrapper(Context context, IKeyguardService service,
+ OnShowingStateChangedCallback showingStateChangedCallback) {
mService = service;
- mKeyguardStateMonitor = new KeyguardStateMonitor(context, service);
+ mKeyguardStateMonitor = new KeyguardStateMonitor(context, service,
+ showingStateChangedCallback);
}
@Override // Binder interface
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index 08eaaa9..712b625 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -49,10 +49,13 @@
private int mCurrentUserId;
private final LockPatternUtils mLockPatternUtils;
+ private final OnShowingStateChangedCallback mOnShowingStateChangedCallback;
- public KeyguardStateMonitor(Context context, IKeyguardService service) {
+ public KeyguardStateMonitor(Context context, IKeyguardService service,
+ OnShowingStateChangedCallback showingStateChangedCallback) {
mLockPatternUtils = new LockPatternUtils(context);
mCurrentUserId = ActivityManager.getCurrentUser();
+ mOnShowingStateChangedCallback = showingStateChangedCallback;
try {
service.addStateMonitorCallback(this);
} catch (RemoteException e) {
@@ -83,6 +86,7 @@
@Override // Binder interface
public void onShowingStateChanged(boolean showing) {
mIsShowing = showing;
+ mOnShowingStateChangedCallback.onShowingStateChanged(showing);
}
@Override // Binder interface
@@ -122,4 +126,8 @@
pw.println(prefix + "mTrusted=" + mTrusted);
pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
}
+
+ public interface OnShowingStateChangedCallback {
+ void onShowingStateChanged(boolean showing);
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7576cddad..706828b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -31,6 +31,7 @@
import android.hardware.SystemSensorManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.hardware.power.V1_0.PowerHint;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -84,7 +85,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import static android.os.PowerManagerInternal.POWER_HINT_INTERACTION;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -163,10 +163,6 @@
// How long a partial wake lock must be held until we consider it a long wake lock.
static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
- // Power hints defined in hardware/libhardware/include/hardware/power.h.
- private static final int POWER_HINT_LOW_POWER = 5;
- private static final int POWER_HINT_VR_MODE = 7;
-
// Power features defined in hardware/libhardware/include/hardware/power.h.
private static final int POWER_FEATURE_DOUBLE_TAP_TO_WAKE = 1;
@@ -826,7 +822,7 @@
if (mLowPowerModeEnabled != lowPowerModeEnabled) {
mLowPowerModeEnabled = lowPowerModeEnabled;
- powerHintInternal(POWER_HINT_LOW_POWER, lowPowerModeEnabled ? 1 : 0);
+ powerHintInternal(PowerHint.LOW_POWER, lowPowerModeEnabled ? 1 : 0);
postAfterBootCompleted(new Runnable() {
@Override
public void run() {
@@ -1152,7 +1148,7 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, "userActivity");
try {
if (eventTime > mLastInteractivePowerHintTime) {
- powerHintInternal(POWER_HINT_INTERACTION, 0);
+ powerHintInternal(PowerHint.INTERACTION, 0);
mLastInteractivePowerHintTime = eventTime;
}
@@ -3083,7 +3079,7 @@
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
- powerHintInternal(POWER_HINT_VR_MODE, enabled ? 1 : 0);
+ powerHintInternal(PowerHint.VR_MODE, enabled ? 1 : 0);
}
};
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 1dbc6d9..badee82 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -717,6 +717,14 @@
}
if (!done[0]) {
Log.w(TAG, "Timed out waiting for uncrypt.");
+ final int uncryptTimeoutError = 100;
+ String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
+ MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
+ try {
+ FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 552803f..7b7db0e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -28,23 +28,21 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
-import android.view.KeyEvent;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.FastPrintWriter;
import com.android.server.LocalServices;
import com.android.server.notification.NotificationDelegate;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -849,9 +847,9 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new StatusBarShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
// ================================================================================
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index d219aed..353d663 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -872,6 +872,11 @@
} catch (RemoteException e) {
}
}
+ final Intent lockIntent = new Intent(Intent.ACTION_DEVICE_LOCKED_CHANGED);
+ lockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ lockIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mContext.sendBroadcastAsUser(lockIntent, UserHandle.SYSTEM,
+ Manifest.permission.TRUST_LISTENER, /* options */ null);
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index b488297..47414a0 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -915,7 +915,11 @@
public void updateTvInputInfo(TvInputInfo inputInfo, int userId) {
String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
String callingPackageName = getCallingPackageName();
- if (!TextUtils.equals(inputInfoPackageName, callingPackageName)) {
+ if (!TextUtils.equals(inputInfoPackageName, callingPackageName)
+ && mContext.checkCallingPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ // Only the app owning the input and system settings are allowed to update info.
throw new IllegalArgumentException("calling package " + callingPackageName
+ " is not allowed to change TvInputInfo for " + inputInfoPackageName);
}
diff --git a/services/core/java/com/android/server/utils/PriorityDump.java b/services/core/java/com/android/server/utils/PriorityDump.java
new file mode 100644
index 0000000..c05cc3f
--- /dev/null
+++ b/services/core/java/com/android/server/utils/PriorityDump.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.server.utils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
+ * {@link #PRIORITY_ARG} argument.
+ * <p>
+ * Typical usage:
+ *
+ * <pre><code>
+public class SpringfieldNuclearPowerPlant extends Binder {
+
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Donuts in the box: 1");
+ }
+
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+ }
+ };
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
+}
+
+ * </code></pre>
+ *
+ * <strong>Disclaimer</strong>: a real-life service should prioritize core status over donuts :-)
+ *
+ * <p>Then to invoke it:
+ *
+ * <pre><code>
+ *
+ $ adb shell dumpsys snpp
+ Donuts in the box: 1
+ Nuclear reactor status: DANGER - MELTDOWN IMMINENT
+
+ $ adb shell dumpsys snpp --dump_priority CRITICAL
+ Donuts in the box: 1
+
+ $ adb shell dumpsys snpp --dump_priority NORMAL
+ Nuclear reactor status: DANGER - MELTDOWN IMMINENT
+
+ * </code></pre>
+ *
+ *
+ *
+ * <p>To run the unit tests:
+ * <pre><code>
+ *
+ mmm -j32 frameworks/base/services/tests/servicestests/ && \
+ adb install -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && \
+ adb shell am instrument -e class "com.android.server.utils.PriorityDumpTest" \
+ -w "com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner"
+
+ * </code></pre>
+ *
+ *
+ * @hide
+ */
+public final class PriorityDump {
+
+ public static final String PRIORITY_ARG = "--dump_priority";
+
+ private PriorityDump() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
+ * {@code --dump_priority}, stripping the priority and its type.
+ * <p>
+ * For example, if called as {@code --dump_priority HIGH arg1 arg2 arg3}, it will call
+ * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
+ * <p>
+ * If the {@code --dump_priority} is not set, it calls
+ * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
+ * {@code args} instead.
+ */
+ public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw,
+ String[] args) {
+ if (args != null && args.length >= 2 && args[0].equals(PRIORITY_ARG)) {
+ final String priority = args[1];
+ switch (priority) {
+ case "CRITICAL": {
+ dumper.dumpCritical(fd, pw, getStrippedArgs(args));
+ return;
+ }
+ case "HIGH": {
+ dumper.dumpHigh(fd, pw, getStrippedArgs(args));
+ return;
+ }
+ case "NORMAL": {
+ dumper.dumpNormal(fd, pw, getStrippedArgs(args));
+ return;
+ }
+ }
+ }
+ dumper.dump(fd, pw, args);
+ }
+
+ /**
+ * Gets an array without the {@code --dump_priority PRIORITY} prefix.
+ */
+ private static String[] getStrippedArgs(String[] args) {
+ final String[] stripped = new String[args.length - 2];
+ System.arraycopy(args, 2, stripped, 0, stripped.length);
+ return stripped;
+ }
+
+ /**
+ * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
+ * {@link #PRIORITY_ARG} argument.
+ *
+ * @hide
+ */
+ public static interface PriorityDumper {
+
+ /**
+ * Dumps only the critical section.
+ */
+ @SuppressWarnings("unused")
+ default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ }
+
+ /**
+ * Dumps only the high-priority section.
+ */
+ @SuppressWarnings("unused")
+ default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+ }
+
+ /**
+ * Dumps only the normal section.
+ */
+ @SuppressWarnings("unused")
+ default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ }
+
+ /**
+ * Dumps all sections.
+ * <p>
+ * This method is called when
+ * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[])} is
+ * called without priority arguments. By default, it calls the 3 {@code dumpTYPE} methods,
+ * so sub-classes just need to implement the priority types they support.
+ */
+ @SuppressWarnings("unused")
+ default void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ dumpCritical(fd, pw, args);
+ dumpHigh(fd, pw, args);
+ dumpNormal(fd, pw, args);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5514551..7439f535 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -480,6 +480,7 @@
WallpaperData mLastWallpaper;
IWallpaperManagerCallback mKeyguardListener;
boolean mWaitingForUnlock;
+ boolean mShuttingDown;
/**
* ID of the current wallpaper, changed every time anything sets a wallpaper.
@@ -607,6 +608,14 @@
private Runnable mResetRunnable = () -> {
synchronized (mLock) {
+ if (mShuttingDown) {
+ // Don't expect wallpaper services to relaunch during shutdown
+ if (DEBUG) {
+ Slog.i(TAG, "Ignoring relaunch timeout during shutdown");
+ }
+ return;
+ }
+
if (!mWallpaper.wallpaperUpdating
&& mWallpaper.userId == mCurrentUserId) {
Slog.w(TAG, "Wallpaper reconnect timed out, "
@@ -867,6 +876,7 @@
public WallpaperManagerService(Context context) {
if (DEBUG) Slog.v(TAG, "WallpaperService startup");
mContext = context;
+ mShuttingDown = false;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
mIWindowManager = IWindowManager.Stub.asInterface(
@@ -931,6 +941,21 @@
}
}, userFilter);
+ final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+ if (DEBUG) {
+ Slog.i(TAG, "Shutting down");
+ }
+ synchronized (mLock) {
+ mShuttingDown = true;
+ }
+ }
+ }
+ }, shutdownFilter);
+
try {
ActivityManagerNative.getDefault().registerUserSwitchObserver(
new UserSwitchObserver() {
@@ -1012,6 +1037,7 @@
for (String filename : sPerUserFiles) {
new File(wallpaperDir, filename).delete();
}
+ mUserRestorecon.remove(userId);
}
}
@@ -1453,7 +1479,7 @@
lockWP.cropHint.set(sysWP.cropHint);
lockWP.width = sysWP.width;
lockWP.height = sysWP.height;
- lockWP.allowBackup = false;
+ lockWP.allowBackup = sysWP.allowBackup;
// Migrate the bitmap files outright; no need to copy
try {
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/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 846169c..6d97796 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -20,15 +20,16 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.PatternMatcher;
import android.os.Process;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
-import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -140,9 +141,10 @@
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
- FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
(new WebViewUpdateServiceShellCommand(this)).exec(
- this, in, out, err, args, resultReceiver);
+ this, in, out, err, args, callback, resultReceiver);
}
@@ -223,7 +225,13 @@
@Override // Binder call
public String getCurrentWebViewPackageName() {
- return WebViewUpdateService.this.mImpl.getCurrentWebViewPackageName();
+ PackageInfo pi = WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
+ return pi == null ? null : pi.packageName;
+ }
+
+ @Override // Binder call
+ public PackageInfo getCurrentWebViewPackage() {
+ return WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 3a93b46..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) {
@@ -145,8 +155,8 @@
return mSystemInterface.getWebViewPackages();
}
- String getCurrentWebViewPackageName() {
- return mWebViewUpdater.getCurrentWebViewPackageName();
+ PackageInfo getCurrentWebViewPackage() {
+ return mWebViewUpdater.getCurrentWebViewPackage();
}
void enableFallbackLogic(boolean enable) {
@@ -316,6 +326,7 @@
onWebViewProviderChanged(newPackage);
}
} catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
Slog.e(TAG, "Could not find valid WebView package to create " +
"relro with " + e);
}
@@ -371,6 +382,7 @@
providerChanged = (oldPackage == null)
|| !newPackage.packageName.equals(oldPackage.packageName);
} catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " +
"package " + e);
// If we don't perform the user change but don't have an installed WebView
@@ -548,11 +560,9 @@
return new WebViewProviderResponse(webViewPackage, webViewStatus);
}
- public String getCurrentWebViewPackageName() {
+ public PackageInfo getCurrentWebViewPackage() {
synchronized(mLock) {
- if (mCurrentWebViewPackage == null)
- return null;
- return mCurrentWebViewPackage.packageName;
+ return mCurrentWebViewPackage;
}
}
@@ -579,6 +589,7 @@
PackageInfo newPackage = findPreferredWebViewPackage();
onWebViewProviderChanged(newPackage);
} catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
// If we can't find any valid WebView package we are now in a state where
// mAnyWebViewInstalled is false, so loading WebView will be blocked and we
// should simply wait until we receive an intent declaring a new package was
@@ -708,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/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 0605d80..893749e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -654,7 +654,7 @@
final int windowCount = windowList.size();
for (int i = 0; i < windowCount; i++) {
WindowState windowState = windowList.get(i);
- if (windowState.isOnScreen() &&
+ if (windowState.isOnScreen() && windowState.isVisibleLw() &&
!windowState.mWinAnimator.mEnterAnimationPending) {
outWindows.put(windowState.mLayer, windowState);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index e4ec295..cd46165 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -374,6 +374,7 @@
void goodToGo(AppWindowAnimator topOpeningAppAnimator, AppWindowAnimator topClosingAppAnimator,
ArraySet<AppWindowToken> openingApps, ArraySet<AppWindowToken> closingApps) {
+ int appTransition = mNextAppTransition;
mNextAppTransition = TRANSIT_UNSET;
mAppTransitionState = APP_STATE_RUNNING;
notifyAppTransitionStartingLocked(
@@ -382,7 +383,7 @@
topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null,
topClosingAppAnimator != null ? topClosingAppAnimator.animation : null);
mService.getDefaultDisplayContentLocked().getDockedDividerController()
- .notifyAppTransitionStarting(openingApps);
+ .notifyAppTransitionStarting(openingApps, appTransition);
// Prolong the start for the transition when docking a task from recents, unless recents
// ended it already then we don't need to wait.
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 305e47f..e774259 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
@@ -30,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;
@@ -332,7 +332,7 @@
}
// This must be called while inside a transaction.
- boolean stepAnimationLocked(long currentTime, final int displayId) {
+ boolean stepAnimationLocked(long currentTime) {
if (mService.okToDisplay()) {
// We will run animations as long as the display isn't frozen.
@@ -383,8 +383,7 @@
return false;
}
- mAppToken.setAppLayoutChanges(
- WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, "AppWindowToken", displayId);
+ mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken");
clearAnimation();
animating = false;
@@ -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 ba26e13..d46b535 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -35,6 +35,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_ACTIVITY_DRAWN;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_STARTING_WINDOW_DRAWN;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.logWithStack;
@@ -91,19 +92,22 @@
// an activity have been drawn, so they can be made visible together
// at the same time.
// initialize so that it doesn't match mTransactionSequence which is an int.
- long lastTransactionSequence = Long.MIN_VALUE;
- int numInterestingWindows;
- int numDrawnWindows;
+ private long mLastTransactionSequence = Long.MIN_VALUE;
+ private int mNumInterestingWindows;
+ private int mNumDrawnWindows;
boolean inPendingTransaction;
boolean allDrawn;
// Set to true when this app creates a surface while in the middle of an animation. In that
// case do not clear allDrawn until the animation completes.
boolean deferClearAllDrawn;
- // These are to track the app's real drawing status if there were no saved surfaces.
+ /**
+ * These are to track the app's real drawing status if there were no saved surfaces.
+ * @see #updateDrawnWindowStates
+ */
boolean allDrawnExcludingSaved;
- int numInterestingWindowsExcludingSaved;
- int numDrawnWindowsExcludingSaved;
+ private int mNumInterestingWindowsExcludingSaved;
+ private int mNumDrawnWindowsExcludingSaved;
// Is this window's surface needed? This is almost like hidden, except
// it will sometimes be true a little earlier: when the token has
@@ -118,7 +122,7 @@
boolean reportedVisible;
// Last drawn state we reported to the app token.
- boolean reportedDrawn;
+ private boolean reportedDrawn;
// Set to true when the token has been removed from the window mgr.
boolean removed;
@@ -146,7 +150,7 @@
boolean mAppStopped;
int mRotationAnimationHint;
- int mPendingRelaunchCount;
+ private int mPendingRelaunchCount;
private ArrayList<WindowSurfaceController.SurfaceControlWithBackground> mSurfaceViewBackgrounds =
new ArrayList<>();
@@ -154,8 +158,10 @@
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
- AppWindowToken(WindowManagerService service, IApplicationToken token, boolean _voiceInteraction) {
- super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true);
+ AppWindowToken(WindowManagerService service, IApplicationToken token, boolean _voiceInteraction,
+ DisplayContent displayContent) {
+ super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true,
+ displayContent);
appToken = token;
voiceInteraction = _voiceInteraction;
mInputApplicationHandle = new InputApplicationHandle(this);
@@ -264,7 +270,7 @@
final WindowState window = findMainWindow();
//TODO (multidisplay): Magnification is supported only for the default display.
if (window != null && accessibilityController != null
- && window.getDisplayId() == DEFAULT_DISPLAY) {
+ && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
accessibilityController.onAppWindowTransitionLocked(window, transit);
}
changed = true;
@@ -364,11 +370,9 @@
@Override
boolean isVisible() {
- if (hidden) {
- // TODO: Should this be checking hiddenRequested instead of hidden?
- return false;
- }
- return super.isVisible();
+ // If the app token isn't hidden then it is considered visible and there is no need to check
+ // its children windows to see if they are visible.
+ return !hidden;
}
@Override
@@ -414,22 +418,14 @@
* surfaces that's eligible, if the app is already stopped.
*/
private void destroySurfaces(boolean cleanupOnResume) {
- final DisplayContentList displayList = new DisplayContentList();
+ boolean destroyedSomething = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState win = mChildren.get(i);
- final boolean destroyed = win.destroySurface(cleanupOnResume, mAppStopped);
-
- if (destroyed) {
- final DisplayContent displayContent = win.getDisplayContent();
- if (displayContent != null && !displayList.contains(displayContent)) {
- displayList.add(displayContent);
- }
- }
+ destroyedSomething |= win.destroySurface(cleanupOnResume, mAppStopped);
}
- for (int i = 0; i < displayList.size(); i++) {
- final DisplayContent displayContent = displayList.get(i);
- mService.mLayersController.assignLayersLocked(displayContent.getWindowList());
- displayContent.layoutNeeded = true;
+ if (destroyedSomething) {
+ final DisplayContent dc = getDisplayContent();
+ dc.assignWindowLayers(true /*setLayoutNeeded*/);
}
}
@@ -696,7 +692,7 @@
}
}
- boolean waitingForReplacement() {
+ private boolean waitingForReplacement() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState candidate = mChildren.get(i);
if (candidate.waitingForReplacement()) {
@@ -729,9 +725,7 @@
if (mTask.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
// We didn't call prepareFreezingBounds on the task, so use the current value.
- final Configuration config = new Configuration(mService.mGlobalConfiguration);
- config.updateFrom(mTask.mOverrideConfig);
- mFrozenMergedConfig.offer(config);
+ mFrozenMergedConfig.offer(new Configuration(mTask.getConfiguration()));
} else {
mFrozenMergedConfig.offer(new Configuration(mTask.mPreparedFrozenMergedConfig));
}
@@ -792,6 +786,9 @@
void overridePlayingAppAnimations(Animation a) {
if (mAppAnimator.isAnimating()) {
final WindowState win = findMainWindow();
+ if (win == null) {
+ return;
+ }
final int width = win.mContainingFrame.width();
final int height = win.mContainingFrame.height();
mAppAnimator.setAnimation(a, width, height, false, STACK_CLIP_NONE);
@@ -811,17 +808,12 @@
}
}
- void setAppLayoutChanges(int changes, String reason, int displayId) {
- final WindowAnimator windowAnimator = mAppAnimator.mAnimator;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- // Child windows will be on the same display as their parents.
- if (displayId == (mChildren.get(i)).getDisplayId()) {
- windowAnimator.setPendingLayoutChanges(displayId, changes);
- if (DEBUG_LAYOUT_REPEATS) {
- mService.mWindowPlacerLocked.debugLayoutRepeats(
- reason, windowAnimator.getPendingLayoutChanges(displayId));
- }
- break;
+ void setAppLayoutChanges(int changes, String reason) {
+ if (!mChildren.isEmpty()) {
+ final DisplayContent dc = getDisplayContent();
+ dc.pendingLayoutChanges |= changes;
+ if (DEBUG_LAYOUT_REPEATS) {
+ mService.mWindowPlacerLocked.debugLayoutRepeats(reason, dc.pendingLayoutChanges);
}
}
}
@@ -894,7 +886,7 @@
}
boolean transferStartingWindow(IBinder transferFrom) {
- final AppWindowToken fromToken = mService.findAppWindowToken(transferFrom);
+ final AppWindowToken fromToken = getDisplayContent().getAppWindowToken(transferFrom);
if (fromToken == null) {
return false;
}
@@ -926,7 +918,7 @@
if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
"Removing starting window: " + tStartingWindow);
- tStartingWindow.getWindowList().remove(tStartingWindow);
+ getDisplayContent().getWindowList().remove(tStartingWindow);
mService.mWindowsChanged = true;
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
"Removing starting " + tStartingWindow + " from " + fromToken);
@@ -957,7 +949,7 @@
mService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
- mService.getDefaultDisplayContentLocked().layoutNeeded = true;
+ getDisplayContent().setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
Binder.restoreCallingIdentity(origId);
return true;
@@ -1023,7 +1015,7 @@
}
@Override
- void checkAppWindowsReadyToShow(int displayId) {
+ void checkAppWindowsReadyToShow() {
if (allDrawn == mAppAnimator.allDrawn) {
return;
}
@@ -1039,12 +1031,12 @@
stopFreezingScreen(false, true);
if (DEBUG_ORIENTATION) Slog.i(TAG,
"Setting mOrientationChangeComplete=true because wtoken " + this
- + " numInteresting=" + numInterestingWindows + " numDrawn=" + numDrawnWindows);
+ + " numInteresting=" + mNumInterestingWindows + " numDrawn=" + mNumDrawnWindows);
// This will set mOrientationChangeComplete and cause a pass through layout.
setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
- "checkAppWindowsReadyToShow: freezingScreen", displayId);
+ "checkAppWindowsReadyToShow: freezingScreen");
} else {
- setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow", displayId);
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
// We can now show all of the drawn windows!
if (!mService.mOpeningApps.contains(this)) {
@@ -1053,30 +1045,28 @@
}
}
- @Override
- void updateAllDrawn(int displayId) {
- final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(displayId);
-
+ void updateAllDrawn(DisplayContent dc) {
if (!allDrawn) {
- final int numInteresting = numInterestingWindows;
- if (numInteresting > 0 && numDrawnWindows >= numInteresting) {
+ final int numInteresting = mNumInterestingWindows;
+ if (numInteresting > 0 && mNumDrawnWindows >= numInteresting) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawn: " + this
- + " interesting=" + numInteresting + " drawn=" + numDrawnWindows);
+ + " interesting=" + numInteresting + " drawn=" + mNumDrawnWindows);
allDrawn = true;
// Force an additional layout pass where
// WindowStateAnimator#commitFinishDrawingLocked() will call performShowLocked().
- displayContent.layoutNeeded = true;
+ dc.setLayoutNeeded();
mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN, token).sendToTarget();
}
}
+
if (!allDrawnExcludingSaved) {
- int numInteresting = numInterestingWindowsExcludingSaved;
- if (numInteresting > 0 && numDrawnWindowsExcludingSaved >= numInteresting) {
+ int numInteresting = mNumInterestingWindowsExcludingSaved;
+ if (numInteresting > 0 && mNumDrawnWindowsExcludingSaved >= numInteresting) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawnExcludingSaved: " + this
+ " interesting=" + numInteresting
- + " drawn=" + numDrawnWindowsExcludingSaved);
+ + " drawn=" + mNumDrawnWindowsExcludingSaved);
allDrawnExcludingSaved = true;
- displayContent.layoutNeeded = true;
+ dc.setLayoutNeeded();
if (isAnimatingInvisibleWithSavedSurface()
&& !mService.mFinishedEarlyAnim.contains(this)) {
mService.mFinishedEarlyAnim.add(this);
@@ -1085,31 +1075,111 @@
}
}
+ /**
+ * Updated this app token tracking states for interesting and drawn windows based on the window.
+ *
+ * @return Returns true if the input window is considered interesting and drawn while all the
+ * windows in this app token where not considered drawn as of the last pass.
+ */
+ boolean updateDrawnWindowStates(WindowState w) {
+ if (DEBUG_STARTING_WINDOW && w == startingWindow) {
+ Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
+ + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
+ }
+
+ if (allDrawn && allDrawnExcludingSaved && !mAppAnimator.freezingScreen) {
+ return false;
+ }
+
+ if (mLastTransactionSequence != mService.mTransactionSequence) {
+ mLastTransactionSequence = mService.mTransactionSequence;
+ mNumInterestingWindows = mNumDrawnWindows = 0;
+ mNumInterestingWindowsExcludingSaved = 0;
+ mNumDrawnWindowsExcludingSaved = 0;
+ startingDisplayed = false;
+ }
+
+ final WindowStateAnimator winAnimator = w.mWinAnimator;
+
+ boolean isInterestingAndDrawn = false;
+
+ if (!allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) {
+ if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
+ Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
+ + ", isAnimationSet=" + winAnimator.isAnimationSet());
+ if (!w.isDrawnLw()) {
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ + " pv=" + w.mPolicyVisibility
+ + " mDrawState=" + winAnimator.drawStateToString()
+ + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
+ + " a=" + winAnimator.mAnimating);
+ }
+ }
+
+ if (w != startingWindow) {
+ if (w.isInteresting()) {
+ mNumInterestingWindows++;
+ if (w.isDrawnLw()) {
+ mNumDrawnWindows++;
+
+ if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: "
+ + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
+ + " freezingScreen=" + mAppAnimator.freezingScreen
+ + " mAppFreezing=" + w.mAppFreezing);
+
+ isInterestingAndDrawn = true;
+ }
+ }
+ } else if (w.isDrawnLw()) {
+ mService.mH.sendEmptyMessage(NOTIFY_STARTING_WINDOW_DRAWN);
+ startingDisplayed = true;
+ }
+ }
+
+ if (!allDrawnExcludingSaved && w.mightAffectAllDrawn(true /* visibleOnly */)) {
+ if (w != startingWindow && w.isInteresting()) {
+ mNumInterestingWindowsExcludingSaved++;
+ if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) {
+ mNumDrawnWindowsExcludingSaved++;
+
+ if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG,
+ "tokenMayBeDrawnExcludingSaved: " + this + " w=" + w
+ + " numInteresting=" + mNumInterestingWindowsExcludingSaved
+ + " freezingScreen=" + mAppAnimator.freezingScreen
+ + " mAppFreezing=" + w.mAppFreezing);
+
+ isInterestingAndDrawn = true;
+ }
+ }
+ }
+
+ return isInterestingAndDrawn;
+ }
+
@Override
- void stepAppWindowsAnimation(long currentTime, int displayId) {
+ void stepAppWindowsAnimation(long currentTime) {
mAppAnimator.wasAnimating = mAppAnimator.animating;
- if (mAppAnimator.stepAnimationLocked(currentTime, displayId)) {
+ if (mAppAnimator.stepAnimationLocked(currentTime)) {
mAppAnimator.animating = true;
mService.mAnimator.setAnimating(true);
mService.mAnimator.mAppWindowAnimating = true;
} else if (mAppAnimator.wasAnimating) {
// stopped animating, do one more pass through the layout
- setAppLayoutChanges(
- FINISH_LAYOUT_REDO_WALLPAPER, "appToken " + this + " done", displayId);
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "appToken " + this + " done");
if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
}
}
- int rebuildWindowListUnchecked(DisplayContent dc, int addIndex) {
- return super.rebuildWindowList(dc, addIndex);
+ int rebuildWindowListUnchecked(int addIndex) {
+ return super.rebuildWindowList(addIndex);
}
@Override
- int rebuildWindowList(DisplayContent dc, int addIndex) {
+ int rebuildWindowList(int addIndex) {
if (mIsExiting && !waitingForReplacement()) {
return addIndex;
}
- return rebuildWindowListUnchecked(dc, addIndex);
+ return rebuildWindowListUnchecked(addIndex);
}
@Override
@@ -1146,11 +1216,11 @@
if (mAppStopped) {
pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped);
}
- if (numInterestingWindows != 0 || numDrawnWindows != 0
+ if (mNumInterestingWindows != 0 || mNumDrawnWindows != 0
|| allDrawn || mAppAnimator.allDrawn) {
- pw.print(prefix); pw.print("numInterestingWindows=");
- pw.print(numInterestingWindows);
- pw.print(" numDrawnWindows="); pw.print(numDrawnWindows);
+ pw.print(prefix); pw.print("mNumInterestingWindows=");
+ pw.print(mNumInterestingWindows);
+ pw.print(" mNumDrawnWindows="); pw.print(mNumDrawnWindows);
pw.print(" inPendingTransaction="); pw.print(inPendingTransaction);
pw.print(" allDrawn="); pw.print(allDrawn);
pw.print(" (animator="); pw.print(mAppAnimator.allDrawn);
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 7f97c46..da2c6a7 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -256,7 +256,7 @@
// on whether a dim layer is showing or not.
if (targetAlpha == 0) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
}
} else if (state.dimLayer.getLayer() != dimLayer) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a09c597..7cd9971 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -20,54 +20,111 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
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;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.hardware.display.DisplayManagerInternal;
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.Surface;
+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;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
-class DisplayContentList extends ArrayList<DisplayContent> {
-}
-
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
* particular Display.
@@ -75,15 +132,25 @@
* IMPORTANT: No method from this class should ever be used without holding
* WindowManagerService.mWindowMap.
*/
-class DisplayContent extends WindowContainer<TaskStack> {
+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;
+ // The display only has 2 child window containers. mTaskStackContainers which contains all
+ // window containers that are related to apps (Activities) and mNonAppWindowContainers which
+ // contains all window containers not related to apps (e.g. Status bar).
+ private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
+ private final NonAppWindowContainers mNonAppWindowContainers = new NonAppWindowContainers();
+
/** Z-ordered (bottom-most first) list of all Window objects. Assigned to an element
* from mDisplayWindows; */
private final WindowList mWindows = new WindowList();
+ // Mapping from a token IBinder to a WindowToken object on this display.
+ private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
+
int mInitialDisplayWidth = 0;
int mInitialDisplayHeight = 0;
int mInitialDisplayDensity = 0;
@@ -99,7 +166,7 @@
private Rect mContentRect = new Rect();
// Accessed directly by all users.
- boolean layoutNeeded;
+ private boolean mLayoutNeeded;
int pendingLayoutChanges;
final boolean isDefaultDisplay;
@@ -119,12 +186,14 @@
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
+ private final RectF mTmpRectF = new RectF();
+ private final Matrix mTmpMatrix = new Matrix();
private final Region mTmpRegion = new Region();
final WindowManagerService mService;
/** Remove this display when animation on it has completed. */
- boolean mDeferredRemoval;
+ private boolean mDeferredRemoval;
final DockedStackDividerController mDividerControllerLocked;
@@ -140,20 +209,39 @@
private final GetWindowOnDisplaySearchResult mTmpGetWindowOnDisplaySearchResult =
new GetWindowOnDisplaySearchResult();
+ // True if this display is in the process of being removed. Used to determine if the removal of
+ // 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 == Display.DEFAULT_DISPLAY;
+ isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mService = service;
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
mDimLayerController = new DimLayerController(this);
+
+ // These are the only direct children we should ever have and they are permanent.
+ super.addChild(mTaskStackContainers, null);
+ super.addChild(mNonAppWindowContainers, null);
}
int getDisplayId() {
@@ -164,6 +252,45 @@
return mWindows;
}
+ WindowToken getWindowToken(IBinder binder) {
+ return mTokenMap.get(binder);
+ }
+
+ AppWindowToken getAppWindowToken(IBinder binder) {
+ final WindowToken token = getWindowToken(binder);
+ if (token == null) {
+ return null;
+ }
+ return token.asAppWindowToken();
+ }
+
+ void setWindowToken(IBinder binder, WindowToken token) {
+ final DisplayContent dc = mService.mRoot.getWindowTokenDisplay(token);
+ if (dc != null) {
+ // We currently don't support adding a window token to the display if the display
+ // already has the binder mapped to another token. If there is a use case for supporting
+ // this moving forward we will either need to merge the WindowTokens some how or have
+ // the binder map to a list of window tokens.
+ throw new IllegalArgumentException("Can't map token=" + token + " to display=" + this
+ + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
+ }
+ mTokenMap.put(binder, token);
+
+ if (token.asAppWindowToken() == null) {
+ // Add non-app token to container hierarchy on the display. App tokens are added through
+ // the parent container managing them (e.g. Tasks).
+ mNonAppWindowContainers.addChild(token, null);
+ }
+ }
+
+ WindowToken removeWindowToken(IBinder binder) {
+ final WindowToken token = mTokenMap.remove(binder);
+ if (token != null && token.asAppWindowToken() == null) {
+ mNonAppWindowContainers.removeChild(token);
+ }
+ return token;
+ }
+
Display getDisplay() {
return mDisplay;
}
@@ -188,19 +315,19 @@
}
boolean isPrivate() {
- return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0;
+ return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
}
TaskStack getHomeStack() {
- if (mHomeStack == null && mDisplayId == Display.DEFAULT_DISPLAY) {
+ if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
}
return mHomeStack;
}
TaskStack getStackById(int stackId) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
if (stack.mStackId == stackId) {
return stack;
}
@@ -208,35 +335,31 @@
return null;
}
- /** Callback used to notify about configuration changes. */
- void onConfigurationChanged(@NonNull List<Integer> changedStackList) {
+ @Override
+ void onConfigurationChanged(Configuration newParentConfig) {
+ super.onConfigurationChanged(newParentConfig);
+
// The display size information is heavily dependent on the resources in the current
// configuration, so we need to reconfigure it every time the configuration changes.
// See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
mService.reconfigureDisplayLocked(this);
getDockedDividerController().onConfigurationChanged();
+ }
- for (int i = 0; i < mChildren.size(); i++) {
- final TaskStack stack = mChildren.get(i);
- if (stack.onConfigurationChanged()) {
+ /**
+ * Callback used to trigger bounds update after configuration change and get ids of stacks whose
+ * bounds were updated.
+ */
+ void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) {
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
+ if (stack.updateBoundsAfterConfigChange()) {
changedStackList.add(stack.mStackId);
}
}
}
- void checkAppWindowsReadyToShow() {
- super.checkAppWindowsReadyToShow(mDisplayId);
- }
-
- void updateAllDrawn() {
- super.updateAllDrawn(mDisplayId);
- }
-
- void stepAppWindowsAnimation(long currentTime) {
- super.stepAppWindowsAnimation(currentTime, mDisplayId);
- }
-
@Override
boolean fillsParent() {
return true;
@@ -255,40 +378,86 @@
@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) {
- 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() {
mDisplay.getDisplayInfo(mDisplayInfo);
mDisplay.getMetrics(mDisplayMetrics);
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).updateDisplayInfo(null);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ mTaskStackContainers.get(i).updateDisplayInfo(null);
}
}
@@ -311,8 +480,7 @@
void getLogicalDisplayRect(Rect out) {
// Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
final int orientation = mDisplayInfo.rotation;
- boolean rotated = (orientation == Surface.ROTATION_90
- || orientation == Surface.ROTATION_270);
+ boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
int width = mDisplayInfo.logicalWidth;
@@ -322,51 +490,53 @@
out.set(left, top, left + width, top + height);
}
+ private void getLogicalDisplayRect(Rect out, int orientation) {
+ getLogicalDisplayRect(out);
+
+ // Rotate the Rect if needed.
+ final int currentRotation = mDisplayInfo.rotation;
+ final int rotationDelta = deltaRotation(currentRotation, orientation);
+ if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
+ createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
+ mTmpRectF.set(out);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.round(out);
+ }
+ }
+
void getContentRect(Rect out) {
out.set(mContentRect);
}
/** Refer to {@link WindowManagerService#attachStack(int, int, boolean)} */
void attachStack(TaskStack stack, boolean onTop) {
- if (stack.mStackId == HOME_STACK_ID) {
- if (mHomeStack != null) {
- throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
- }
- mHomeStack = stack;
- }
- addChild(stack, onTop);
+ mTaskStackContainers.attachStack(stack, onTop);
}
void moveStack(TaskStack stack, boolean toTop) {
- if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
- // This stack is always-on-top silly...
- Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + stack + " to bottom");
- return;
- }
-
- if (!mChildren.contains(stack)) {
- Slog.wtf(TAG_WM, "moving stack that was not added: " + stack, new Throwable());
- }
- removeChild(stack);
- addChild(stack, toTop);
+ mTaskStackContainers.moveStack(stack, toTop);
}
- private void addChild(TaskStack stack, boolean toTop) {
- int addIndex = toTop ? mChildren.size() : 0;
+ @Override
+ protected void addChild(DisplayChildWindowContainer child,
+ Comparator<DisplayChildWindowContainer> comparator) {
+ throw new UnsupportedOperationException("See DisplayChildWindowContainer");
+ }
- if (toTop
- && mService.isStackVisibleLocked(PINNED_STACK_ID)
- && stack.mStackId != PINNED_STACK_ID) {
- // The pinned stack is always the top most stack (always-on-top) when it is visible.
- // So, stack is moved just below the pinned stack.
- addIndex--;
- TaskStack topStack = mChildren.get(addIndex);
- if (topStack.mStackId != PINNED_STACK_ID) {
- throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
- }
+ @Override
+ protected void addChild(DisplayChildWindowContainer child, int index) {
+ throw new UnsupportedOperationException("See DisplayChildWindowContainer");
+ }
+
+ @Override
+ protected void removeChild(DisplayChildWindowContainer child) {
+ // Only allow removal of direct children from this display if the display is in the process
+ // of been removed.
+ if (mRemovingDisplay) {
+ super.removeChild(child);
+ return;
}
- addChild(stack, addIndex);
- layoutNeeded = true;
+ throw new UnsupportedOperationException("See DisplayChildWindowContainer");
}
/**
@@ -378,8 +548,8 @@
}
int taskIdFromPoint(int x, int y) {
- for (int stackNdx = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mChildren.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.get(stackNdx);
final int taskId = stack.taskIdFromPoint(x, y);
if (taskId != -1) {
return taskId;
@@ -393,10 +563,10 @@
* Returns null if the touch doesn't fall into a resizing area.
*/
Task findTaskForResizePoint(int x, int y) {
- final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+ final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
mTmpTaskForResizePointSearchResult.reset();
- for (int stackNdx = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- TaskStack stack = mChildren.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.get(stackNdx);
if (!StackId.isTaskResizeAllowed(stack.mStackId)) {
return null;
}
@@ -411,10 +581,10 @@
void setTouchExcludeRegion(Task focusedTask) {
mTouchExcludeRegion.set(mBaseDisplayRect);
- final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+ final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
mTmpRect2.setEmpty();
- for (int stackNdx = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mChildren.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.get(stackNdx);
stack.setTouchExcludeRegion(
focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
}
@@ -458,16 +628,16 @@
}
}
- for (int stackNdx = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- mChildren.get(stackNdx).switchUser();
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ mTaskStackContainers.get(stackNdx).switchUser();
}
rebuildAppWindowList();
}
void resetAnimationBackgroundAnimator() {
- for (int stackNdx = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- mChildren.get(stackNdx).resetAnimationBackgroundAnimator();
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ mTaskStackContainers.get(stackNdx).resetAnimationBackgroundAnimator();
}
}
@@ -498,12 +668,17 @@
@Override
void removeImmediately() {
- super.removeImmediately();
- if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
- mDimLayerController.close();
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mService.unregisterPointerEventListener(mTapDetector);
- mService.unregisterPointerEventListener(mService.mMousePositionTracker);
+ mRemovingDisplay = true;
+ try {
+ super.removeImmediately();
+ if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
+ mDimLayerController.close();
+ if (mDisplayId == DEFAULT_DISPLAY) {
+ mService.unregisterPointerEventListener(mTapDetector);
+ mService.unregisterPointerEventListener(mService.mMousePositionTracker);
+ }
+ } finally {
+ mRemovingDisplay = false;
}
}
@@ -524,8 +699,8 @@
float dividerAnimationTarget) {
boolean updated = false;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
if (stack == null || !stack.isAdjustedForIme()) {
continue;
}
@@ -553,8 +728,8 @@
boolean clearImeAdjustAnimation() {
boolean changed = false;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
if (stack != null && stack.isAdjustedForIme()) {
stack.resetAdjustedForIme(true /* adjustBoundsNow */);
changed = true;
@@ -564,8 +739,8 @@
}
void beginImeAdjustAnimation() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
if (stack.isVisible() && stack.isAdjustedForIme()) {
stack.beginImeAdjustAnimation();
}
@@ -594,8 +769,8 @@
// - If IME is not visible, divider is not moved and is normal width.
if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)) {
stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
@@ -606,8 +781,8 @@
mDividerControllerLocked.setAdjustedForIme(
imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
} else {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
stack.resetAdjustedForIme(!dockVisible);
}
mDividerControllerLocked.setAdjustedForIme(
@@ -615,40 +790,57 @@
}
}
+ 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 = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mChildren.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.get(stackNdx);
stack.prepareFreezingTaskBounds();
}
}
void rotateBounds(int oldRotation, int newRotation, Rect bounds) {
- final int rotationDelta = DisplayContent.deltaRotation(oldRotation, newRotation);
- getLogicalDisplayRect(mTmpRect);
- switch (rotationDelta) {
- case Surface.ROTATION_0:
- mTmpRect2.set(bounds);
- break;
- case Surface.ROTATION_90:
- mTmpRect2.top = mTmpRect.bottom - bounds.right;
- mTmpRect2.left = bounds.top;
- mTmpRect2.right = mTmpRect2.left + bounds.height();
- mTmpRect2.bottom = mTmpRect2.top + bounds.width();
- break;
- case Surface.ROTATION_180:
- mTmpRect2.top = mTmpRect.bottom - bounds.bottom;
- mTmpRect2.left = mTmpRect.right - bounds.right;
- mTmpRect2.right = mTmpRect2.left + bounds.width();
- mTmpRect2.bottom = mTmpRect2.top + bounds.height();
- break;
- case Surface.ROTATION_270:
- mTmpRect2.top = bounds.left;
- mTmpRect2.left = mTmpRect.right - bounds.bottom;
- mTmpRect2.right = mTmpRect2.left + bounds.height();
- mTmpRect2.bottom = mTmpRect2.top + bounds.width();
- break;
- }
- bounds.set(mTmpRect2);
+ getLogicalDisplayRect(mTmpRect, newRotation);
+
+ // Compute a transform matrix to undo the coordinate space transformation,
+ // and present the window at the same physical position it previously occupied.
+ final int deltaRotation = deltaRotation(newRotation, oldRotation);
+ createRotationMatrix(deltaRotation, mTmpRect.width(), mTmpRect.height(), mTmpMatrix);
+
+ mTmpRectF.set(bounds);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.round(bounds);
}
static int deltaRotation(int oldRotation, int newRotation) {
@@ -657,6 +849,35 @@
return delta;
}
+ private static void createRotationMatrix(int rotation, float displayWidth, float displayHeight,
+ Matrix outMatrix) {
+ // For rotations without Z-ordering we don't need the target rectangle's position.
+ createRotationMatrix(rotation, 0 /* rectLeft */, 0 /* rectTop */, displayWidth,
+ displayHeight, outMatrix);
+ }
+
+ static void createRotationMatrix(int rotation, float rectLeft, float rectTop,
+ float displayWidth, float displayHeight, Matrix outMatrix) {
+ switch (rotation) {
+ case ROTATION_0:
+ outMatrix.reset();
+ break;
+ case ROTATION_270:
+ outMatrix.setRotate(270, 0, 0);
+ outMatrix.postTranslate(0, displayHeight);
+ outMatrix.postTranslate(rectTop, 0);
+ break;
+ case ROTATION_180:
+ outMatrix.reset();
+ break;
+ case ROTATION_90:
+ outMatrix.setRotate(90, 0, 0);
+ outMatrix.postTranslate(displayWidth, 0);
+ outMatrix.postTranslate(-rectTop, rectLeft);
+ break;
+ }
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
final String subPrefix = " " + prefix;
@@ -683,13 +904,13 @@
pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
- pw.print(subPrefix); pw.print("deferred="); pw.print(mDeferredRemoval);
- pw.print(" layoutNeeded="); pw.println(layoutNeeded);
+ pw.println(subPrefix + "deferred=" + mDeferredRemoval
+ + " mLayoutNeeded=" + mLayoutNeeded);
pw.println();
pw.println(" Application tokens in top down Z order:");
- for (int stackNdx = mChildren.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mChildren.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.get(stackNdx);
stack.dump(prefix + " ", pw);
}
@@ -698,7 +919,7 @@
pw.println();
pw.println(" Exiting tokens:");
for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
- WindowToken token = mExitingTokens.get(i);
+ final WindowToken token = mExitingTokens.get(i);
pw.print(" Exiting #"); pw.print(i);
pw.print(' '); pw.print(token);
pw.println(':');
@@ -709,15 +930,20 @@
mDimLayerController.dump(prefix + " ", pw);
pw.println();
mDividerControllerLocked.dump(prefix + " ", pw);
+
+ if (mInputMethodAnimLayerAdjustment != 0) {
+ pw.println(subPrefix
+ + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
+ }
}
@Override
public String toString() {
- return getName() + " stacks=" + mChildren;
+ return "Display " + mDisplayId + " info=" + mDisplayInfo + " stacks=" + mChildren;
}
String getName() {
- return "Display " + mDisplayId + " info=" + mDisplayInfo;
+ return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
}
/**
@@ -777,28 +1003,27 @@
for (int i = 0; i < windowCount; i++) {
WindowState window = windows.get(i);
if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
- && !window.mPermanentlyHidden && !window.mAnimatingExit) {
+ && !window.mPermanentlyHidden && !window.mAnimatingExit
+ && !window.mRemoveOnExit) {
return false;
}
}
return true;
}
- void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus,
- WindowState newFocus) {
+ void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus, WindowState newFocus) {
if (oldFocus == null || (newFocus != null && newFocus.mOwnerUid == oldFocus.mOwnerUid)) {
return;
}
final int lostFocusUid = oldFocus.mOwnerUid;
- WindowList windows = getWindowList();
+ final WindowList windows = getWindowList();
final int windowCount = windows.size();
+ final Handler handler = mService.mH;
for (int i = 0; i < windowCount; i++) {
- WindowState window = windows.get(i);
+ final WindowState window = windows.get(i);
if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == lostFocusUid) {
- if (!mService.mH.hasMessages(WindowManagerService.H.WINDOW_HIDE_TIMEOUT, window)) {
- mService.mH.sendMessageDelayed(
- mService.mH.obtainMessage(
- WindowManagerService.H.WINDOW_HIDE_TIMEOUT, window),
+ if (!handler.hasMessages(WINDOW_HIDE_TIMEOUT, window)) {
+ handler.sendMessageDelayed(handler.obtainMessage(WINDOW_HIDE_TIMEOUT, window),
window.mAttrs.hideTimeoutMilliseconds);
}
}
@@ -869,15 +1094,15 @@
}
// No windows from this token on this display
- if (mService.localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window "
+ if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window "
+ client.asBinder() + " (token=" + this + ")");
final WindowToken wToken = win.mToken;
// Figure out where the window should go, based on the order of applications.
mTmpGetWindowOnDisplaySearchResult.reset();
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
stack.getWindowOnDisplayBeforeToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
if (mTmpGetWindowOnDisplaySearchResult.reachedToken) {
// We have reach the token we are interested in. End search.
@@ -891,7 +1116,7 @@
// position; else we need to look some more.
if (pos != null) {
// Move behind any windows attached to this one.
- final WindowToken atoken = mService.mTokenMap.get(pos.mClient.asBinder());
+ final WindowToken atoken = getWindowToken(pos.mClient.asBinder());
if (atoken != null) {
tokenWindowList = getTokenWindowsOnDisplay(atoken);
final int NC = tokenWindowList.size();
@@ -908,8 +1133,8 @@
// Continue looking down until we find the first token that has windows on this display.
mTmpGetWindowOnDisplaySearchResult.reset();
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final TaskStack stack = mChildren.get(i);
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
stack.getWindowOnDisplayAfterToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
if (mTmpGetWindowOnDisplaySearchResult.foundWindow != null) {
// We have found a window after the token. End search.
@@ -921,7 +1146,7 @@
if (pos != null) {
// Move in front of any windows attached to this one.
- final WindowToken atoken = mService.mTokenMap.get(pos.mClient.asBinder());
+ final WindowToken atoken = getWindowToken(pos.mClient.asBinder());
if (atoken != null) {
final WindowState top = atoken.getTopWindow();
if (top != null && top.mSubLayer >= 0) {
@@ -979,6 +1204,40 @@
mService.mWindowsChanged = true;
}
+ void addToWindowList(WindowState win, int index) {
+ 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();
@@ -1021,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>
@@ -1073,23 +1346,23 @@
// First add all of the exiting app tokens... these are no longer in the main app list,
// but still have windows shown. We put them in the back because now that the animation is
// over we no longer will care about them.
- final int numStacks = mChildren.size();
+ final int numStacks = mTaskStackContainers.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- AppTokenList exitingAppTokens = mChildren.get(stackNdx).mExitingAppTokens;
+ AppTokenList exitingAppTokens = mTaskStackContainers.get(stackNdx).mExitingAppTokens;
int NT = exitingAppTokens.size();
for (int j = 0; j < NT; j++) {
- i = exitingAppTokens.get(j).rebuildWindowListUnchecked(this, i);
+ i = exitingAppTokens.get(j).rebuildWindowListUnchecked(i);
}
}
// And add in the still active app tokens in Z order.
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- i = mChildren.get(stackNdx).rebuildWindowList(this, i);
+ i = mTaskStackContainers.get(stackNdx).rebuildWindowList(i);
}
i -= lastBelow;
if (i != numRemoved) {
- layoutNeeded = true;
+ setLayoutNeeded();
Slog.w(TAG_WM, "On display=" + mDisplayId + " Rebuild removed " + numRemoved
+ " windows but added " + i + " rebuildAppWindowListLocked() "
+ " callers=" + Debug.getCallers(10));
@@ -1126,6 +1399,385 @@
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;
+ }
+
+ void clearLayoutNeeded() {
+ if (DEBUG_LAYOUT) Slog.w(TAG_WM, "clearLayoutNeeded: callers=" + Debug.getCallers(3));
+ mLayoutNeeded = false;
+ }
+
+ boolean isLayoutNeeded() {
+ return mLayoutNeeded;
+ }
+
private int addAppWindowExisting(WindowState win, WindowList tokenWindowList) {
int tokenWindowsPos;
@@ -1216,6 +1868,572 @@
}
}
+ void dumpTokens(PrintWriter pw, boolean dumpAll) {
+ if (mTokenMap.isEmpty()) {
+ return;
+ }
+ pw.println(" Display #" + mDisplayId);
+ final Iterator<WindowToken> it = mTokenMap.values().iterator();
+ while (it.hasNext()) {
+ final WindowToken token = it.next();
+ pw.print(" ");
+ pw.print(token);
+ if (dumpAll) {
+ pw.println(':');
+ token.dump(pw, " ");
+ } else {
+ pw.println();
+ }
+ }
+ }
+
+ 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);
+ win.mWinAnimator.enableSurfaceTrace(fd);
+ }
+ }
+
+ void disableSurfaceTrace() {
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mWindows.get(i);
+ win.mWinAnimator.disableSurfaceTrace();
+ }
+ }
+
+ 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;
@@ -1236,17 +2454,120 @@
}
}
- void enableSurfaceTrace(FileDescriptor fd) {
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
- win.mWinAnimator.enableSurfaceTrace(fd);
+ /**
+ * Base class for any direct child window container of {@link #DisplayContent} need to inherit
+ * from. This is mainly a pass through class that allows {@link #DisplayContent} to have
+ * homogeneous children type which is currently required by sub-classes of
+ * {@link WindowContainer} class.
+ */
+ static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {
+
+ int size() {
+ return mChildren.size();
+ }
+
+ E get(int index) {
+ return mChildren.get(index);
+ }
+
+ @Override
+ boolean fillsParent() {
+ return true;
+ }
+
+ @Override
+ boolean isVisible() {
+ return true;
}
}
- void disableSurfaceTrace() {
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mWindows.get(i);
- win.mWinAnimator.disableSurfaceTrace();
+ /**
+ * Window container class that contains all containers on this display relating to Apps.
+ * I.e Activities.
+ */
+ private class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+
+ void attachStack(TaskStack stack, boolean onTop) {
+ if (stack.mStackId == HOME_STACK_ID) {
+ if (mHomeStack != null) {
+ throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
+ }
+ mHomeStack = stack;
+ }
+ addChild(stack, onTop);
+ stack.onDisplayChanged(DisplayContent.this);
}
+
+ void moveStack(TaskStack stack, boolean toTop) {
+ if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
+ // This stack is always-on-top silly...
+ Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + stack + " to bottom");
+ return;
+ }
+
+ if (!mChildren.contains(stack)) {
+ Slog.wtf(TAG_WM, "moving stack that was not added: " + stack, new Throwable());
+ }
+ removeChild(stack);
+ addChild(stack, toTop);
+ }
+
+ private void addChild(TaskStack stack, boolean toTop) {
+ int addIndex = toTop ? mChildren.size() : 0;
+
+ if (toTop
+ && mService.isStackVisibleLocked(PINNED_STACK_ID)
+ && stack.mStackId != PINNED_STACK_ID) {
+ // The pinned stack is always the top most stack (always-on-top) when it is visible.
+ // So, stack is moved just below the pinned stack.
+ addIndex--;
+ TaskStack topStack = mChildren.get(addIndex);
+ if (topStack.mStackId != PINNED_STACK_ID) {
+ throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
+ }
+ }
+ addChild(stack, addIndex);
+ 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;
+ }
+ }
+
+ /**
+ * Window container class that contains all containers on this display that are not related to
+ * Apps. E.g. status bar.
+ */
+ private static class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+
}
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ef8f492..f75f224 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -29,9 +29,11 @@
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.server.wm.AppTransition.TRANSIT_NONE;
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;
@@ -153,7 +155,7 @@
// If the bounds are fullscreen, return the value of the fullscreen configuration
if (bounds == null || (bounds.left == 0 && bounds.top == 0
&& bounds.right == di.logicalWidth && bounds.bottom == di.logicalHeight)) {
- return mService.mGlobalConfiguration.smallestScreenWidthDp;
+ return mDisplayContent.getConfiguration().smallestScreenWidthDp;
}
final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
@@ -190,7 +192,7 @@
}
private void initSnapAlgorithmForRotations() {
- final Configuration baseConfig = mService.mGlobalConfiguration;
+ final Configuration baseConfig = mDisplayContent.getConfiguration();
// Initialize the snap algorithms for all 4 screen orientations.
final Configuration config = new Configuration();
@@ -295,7 +297,7 @@
}
}
- boolean wasVisible() {
+ private boolean wasVisible() {
return mLastVisibility;
}
@@ -355,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);
@@ -369,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);
@@ -412,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();
@@ -441,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);
@@ -473,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;
}
@@ -486,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.
*/
@@ -493,7 +503,7 @@
checkMinimizeChanged(false /* animate */);
}
- void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps) {
+ void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition) {
final boolean wasMinimized = mMinimizedDock;
checkMinimizeChanged(true /* animate */);
@@ -502,7 +512,8 @@
// any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because
// we couldn't retrace the launch of the app in the docked stack to the launch from
// homescreen.
- if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)) {
+ if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
+ && appTransition != TRANSIT_NONE) {
mService.showRecentApps(true /* fromHome */);
}
}
@@ -691,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 a5387bd..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,9 @@
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;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -25,14 +28,17 @@
import android.app.ActivityManagerNative;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import android.view.Display;
import android.view.InputChannel;
+import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputManagerService;
import com.android.server.input.InputWindowHandle;
@@ -69,10 +75,103 @@
private final Object mInputDevicesReadyMonitor = new Object();
private boolean mInputDevicesReady;
+ /**
+ * The set of input consumer added to the window manager by name, which consumes input events
+ * for the windows below it.
+ */
+ private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap();
+
+ private static final class EventReceiverInputConsumer extends InputConsumerImpl
+ implements WindowManagerPolicy.InputConsumer {
+ private InputMonitor mInputMonitor;
+ private final InputEventReceiver mInputEventReceiver;
+
+ EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
+ Looper looper, String name,
+ InputEventReceiver.Factory inputEventReceiverFactory) {
+ super(service, name, null);
+ mInputMonitor = monitor;
+ mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
+ mClientChannel, looper);
+ }
+
+ @Override
+ public void dismiss() {
+ synchronized (mService.mWindowMap) {
+ if (mInputMonitor.destroyInputConsumer(mWindowHandle.name)) {
+ mInputEventReceiver.dispose();
+ }
+ }
+ }
+ }
+
public InputMonitor(WindowManagerService service) {
mService = service;
}
+ void addInputConsumer(String name, InputConsumerImpl consumer) {
+ mInputConsumers.put(name, consumer);
+ updateInputWindowsLw(true /* force */);
+ }
+
+ boolean destroyInputConsumer(String name) {
+ if (disposeInputConsumer(mInputConsumers.remove(name))) {
+ updateInputWindowsLw(true /* force */);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean disposeInputConsumer(InputConsumerImpl consumer) {
+ if (consumer != null) {
+ consumer.disposeChannelsLw();
+ return true;
+ }
+ return false;
+ }
+
+ InputConsumerImpl getInputConsumer(String name) {
+ return mInputConsumers.get(name);
+ }
+
+ void layoutInputConsumers(int dw, int dh) {
+ for (int i = mInputConsumers.size() - 1; i >= 0; i--) {
+ mInputConsumers.valueAt(i).layout(dw, dh);
+ }
+ }
+
+ WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
+ InputEventReceiver.Factory inputEventReceiverFactory) {
+ if (mInputConsumers.containsKey(name)) {
+ throw new IllegalStateException("Existing input consumer found with name: " + name);
+ }
+
+ final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
+ this, looper, name, inputEventReceiverFactory);
+ addInputConsumer(name, consumer);
+ return consumer;
+ }
+
+ void createInputConsumer(String name, InputChannel inputChannel) {
+ if (mInputConsumers.containsKey(name)) {
+ throw new IllegalStateException("Existing input consumer found with name: " + name);
+ }
+
+ final InputConsumerImpl consumer = new InputConsumerImpl(mService, name, inputChannel);
+ switch (name) {
+ case INPUT_CONSUMER_WALLPAPER:
+ consumer.mWindowHandle.hasWallpaper = true;
+ break;
+ case INPUT_CONSUMER_PIP:
+ // The touchable region of the Pip input window is cropped to the bounds of the
+ // stack, and we need FLAG_NOT_TOUCH_MODAL to ensure other events fall through
+ consumer.mWindowHandle.layoutParamsFlags |=
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ break;
+ }
+ addInputConsumer(name, consumer);
+ }
+
/* Notifies the window manager about a broken input channel.
*
* Called by the InputManager.
@@ -286,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 8f533fb1..90e27ea 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -19,8 +19,10 @@
import android.app.AppOpsManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
import android.os.Debug;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -29,7 +31,6 @@
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayInfo;
@@ -42,12 +43,19 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
@@ -58,15 +66,17 @@
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
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_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
@@ -77,10 +87,10 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON;
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_STARTING_WINDOW_DRAWN;
import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
@@ -109,7 +119,7 @@
private long mUserActivityTimeout = -1;
private boolean mUpdateRotation = false;
private boolean mObscured = false;
- boolean mSyswin = false;
+ private boolean mSyswin = false;
// Set to true when the display contains content to show the user.
// When false, the display manager may choose to mirror or blank the display.
private boolean mDisplayHasContent = false;
@@ -119,7 +129,7 @@
// Last window that requires screen wakelock
WindowState mHoldScreenWindow = null;
// Last window that obscures all windows below
- WindowState mObsuringWindow = null;
+ WindowState mObscuringWindow = null;
// Only set while traversing the default display based on its content.
// Affects the behavior of mirroring on secondary displays.
private boolean mObscureApplicationContentOnSecondaryDisplays = false;
@@ -137,6 +147,17 @@
private final ArrayList<Integer> mChangedStackList = new ArrayList();
+ private final LinkedList<AppWindowToken> mTmpUpdateAllDrawn = new LinkedList();
+
+ private final ArrayList<WindowToken> mTmpTokensList = new ArrayList();
+
+ // Collection of binder tokens mapped to their window type we are allowed to create window
+ // tokens for but that are not current attached to any display. We need to track this here
+ // because a binder token can be added through {@link WindowManagerService#addWindowToken},
+ // but we don't know what display windows for the token will be added to until
+ // {@link WindowManagerService#addWindow} is called.
+ private final HashMap<IBinder, Integer> mUnattachedBinderTokens = new HashMap();
+
// State for the RemoteSurfaceTrace system used in testing. If this is enabled SurfaceControl
// instances will be replaced with an instance that writes a binary representation of all
// commands to mSurfaceTraceFd.
@@ -144,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() {
@@ -179,7 +205,7 @@
return dc;
}
- private DisplayContent getDisplayContent(int displayId) {
+ DisplayContent getDisplayContent(int displayId) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent current = mChildren.get(i);
if (current.getDisplayId() == displayId) {
@@ -190,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);
@@ -209,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);
@@ -247,13 +274,11 @@
mService.mStackIdToStack.put(stackId, stack);
if (stackId == DOCKED_STACK_ID) {
- mService.getDefaultDisplayContentLocked().mDividerControllerLocked
- .notifyDockedStackExistsChanged(true);
+ dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
}
if (!attachedToDisplay) {
- stack.attachDisplayContent(dc);
dc.attachStack(stack, onTop);
}
@@ -265,11 +290,11 @@
return bounds;
}
- boolean layoutNeeded() {
+ boolean isLayoutNeeded() {
final int numDisplays = mChildren.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
- if (displayContent.layoutNeeded) {
+ if (displayContent.isLayoutNeeded()) {
return true;
}
}
@@ -338,6 +363,202 @@
return null;
}
+ /** Return the window token associated with the input binder token on the input display */
+ WindowToken getWindowToken(IBinder binder, DisplayContent dc) {
+ final WindowToken token = dc.getWindowToken(binder);
+ if (token != null) {
+ return token;
+ }
+
+ // There is no window token mapped to the binder on the display. Create and map a window
+ // token if it is currently allowed.
+ if (!mUnattachedBinderTokens.containsKey(binder)) {
+ return null;
+ }
+
+ final int type = mUnattachedBinderTokens.get(binder);
+ return new WindowToken(mService, binder, type, true, dc);
+ }
+
+ /** Returns all window tokens mapped to the input binder. */
+ ArrayList<WindowToken> getWindowTokens(IBinder binder) {
+ mTmpTokensList.clear();
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent dc = mChildren.get(i);
+ final WindowToken token = dc.getWindowToken(binder);
+ if (token != null) {
+ mTmpTokensList.add(token);
+ }
+ }
+ return mTmpTokensList;
+ }
+
+ /**
+ * Returns the app window token for the input binder if it exist in the system.
+ * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
+ * AppWindowToken represents an activity which can only exist on one display.
+ */
+ AppWindowToken getAppWindowToken(IBinder binder) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent dc = mChildren.get(i);
+ final AppWindowToken atoken = dc.getAppWindowToken(binder);
+ if (atoken != null) {
+ return atoken;
+ }
+ }
+ return null;
+ }
+
+ /** Returns the display object the input window token is currently mapped on. */
+ DisplayContent getWindowTokenDisplay(WindowToken token) {
+ if (token == null) {
+ return null;
+ }
+
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent dc = mChildren.get(i);
+ final WindowToken current = dc.getWindowToken(token.token);
+ if (current == token) {
+ return dc;
+ }
+ }
+
+ return null;
+ }
+
+ void addWindowToken(IBinder binder, int type) {
+ if (mUnattachedBinderTokens.containsKey(binder)) {
+ Slog.w(TAG_WM, "addWindowToken: Attempted to add existing binder token: " + binder);
+ return;
+ }
+
+ final ArrayList<WindowToken> tokens = getWindowTokens(binder);
+
+ if (!tokens.isEmpty()) {
+ Slog.w(TAG_WM, "addWindowToken: Attempted to add binder token: " + binder
+ + " for already created window tokens: " + tokens);
+ return;
+ }
+
+ mUnattachedBinderTokens.put(binder, type);
+
+ // 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 DisplayContent dc = getDisplayContent(DEFAULT_DISPLAY);
+ final WindowToken token = new WindowToken(mService, binder, type, true, dc);
+ if (type == TYPE_WALLPAPER) {
+ dc.mWallpaperController.addWallpaperToken(token);
+ }
+ }
+
+ ArrayList<WindowToken> removeWindowToken(IBinder binder) {
+ mUnattachedBinderTokens.remove(binder);
+
+ mTmpTokensList.clear();
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent dc = mChildren.get(i);
+ final WindowToken token = dc.removeWindowToken(binder);
+ if (token != null) {
+ mTmpTokensList.add(token);
+ }
+ }
+ return mTmpTokensList;
+ }
+
+ /**
+ * Removed the mapping to the input binder for the system if it no longer as a window token
+ * associated with it on any display.
+ */
+ void removeWindowTokenIfPossible(IBinder binder) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent dc = mChildren.get(i);
+ final WindowToken token = dc.getWindowToken(binder);
+ if (token != null) {
+ return;
+ }
+ }
+
+ mUnattachedBinderTokens.remove(binder);
+ }
+
+ void removeAppToken(IBinder binder) {
+ final ArrayList<WindowToken> removedTokens = removeWindowToken(binder);
+ if (removedTokens == null || removedTokens.isEmpty()) {
+ Slog.w(TAG_WM, "removeAppToken: Attempted to remove non-existing token: " + binder);
+ return;
+ }
+
+ for (int i = removedTokens.size() - 1; i >= 0; --i) {
+ WindowToken wtoken = removedTokens.get(i);
+ AppWindowToken appToken = wtoken.asAppWindowToken();
+
+ if (appToken == null) {
+ Slog.w(TAG_WM,
+ "Attempted to remove non-App token: " + binder + " wtoken=" + wtoken);
+ continue;
+ }
+
+ AppWindowToken startingToken = null;
+
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app token: " + appToken);
+
+ boolean delayed = appToken.setVisibility(null, false, TRANSIT_UNSET, true,
+ appToken.voiceInteraction);
+
+ mService.mOpeningApps.remove(appToken);
+ appToken.waitingToShow = false;
+ if (mService.mClosingApps.contains(appToken)) {
+ delayed = true;
+ } else if (mService.mAppTransition.isTransitionSet()) {
+ mService.mClosingApps.add(appToken);
+ delayed = true;
+ }
+
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + appToken
+ + " delayed=" + delayed
+ + " animation=" + appToken.mAppAnimator.animation
+ + " animating=" + appToken.mAppAnimator.animating);
+
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
+ + appToken + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
+
+ final TaskStack stack = appToken.mTask.mStack;
+ if (delayed && !appToken.isEmpty()) {
+ // set the token aside because it has an active animation to be finished
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM,
+ "removeAppToken make exiting: " + appToken);
+ stack.mExitingAppTokens.add(appToken);
+ appToken.mIsExiting = true;
+ } else {
+ // Make sure there is no animation running on this token, so any windows associated
+ // with it will be removed as soon as their animations are complete
+ appToken.mAppAnimator.clearAnimation();
+ appToken.mAppAnimator.animating = false;
+ appToken.removeIfPossible();
+ }
+
+ appToken.removed = true;
+ if (appToken.startingData != null) {
+ startingToken = appToken;
+ }
+ appToken.stopFreezingScreen(true, true);
+ if (mService.mFocusedApp == appToken) {
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + appToken);
+ mService.mFocusedApp = null;
+ mService.updateFocusedWindowLocked(
+ UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+ mService.mInputMonitor.setFocusedAppLw(null);
+ }
+
+ if (!delayed) {
+ appToken.updateReportedVisibilityLocked();
+ }
+
+ // Will only remove if startingToken non null.
+ mService.scheduleRemoveStartingWindowLocked(startingToken);
+ }
+ }
+
// TODO: Users would have their own window containers under the display container?
void switchUser() {
final int count = mChildren.size();
@@ -347,23 +568,75 @@
}
}
- int[] onConfigurationChanged(Configuration config) {
+ /**
+ * 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;
+ }
+ onConfigurationChanged(newConfiguration);
+ return updateStackBoundsAfterConfigChange();
+ }
+
+ @Override
+ void onConfigurationChanged(Configuration newParentConfig) {
prepareFreezingTaskBounds();
- mService.mGlobalConfiguration = new Configuration(config);
+ super.onConfigurationChanged(newParentConfig);
mService.mPolicy.onConfigurationChanged();
+ }
+ /**
+ * Callback used to trigger bounds update after configuration change and get ids of stacks whose
+ * bounds were updated.
+ */
+ private int[] updateStackBoundsAfterConfigChange() {
mChangedStackList.clear();
final int numDisplays = mChildren.size();
for (int i = 0; i < numDisplays; ++i) {
final DisplayContent dc = mChildren.get(i);
- dc.onConfigurationChanged(mChangedStackList);
+ dc.updateStackBoundsAfterConfigChange(mChangedStackList);
}
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();
@@ -472,9 +745,16 @@
}
void updateInputWindows(InputMonitor inputMonitor, WindowState inputFocus, boolean inDrag) {
- boolean addInputConsumerHandle = mService.mInputConsumer != null;
- boolean addWallpaperInputConsumerHandle = mService.mWallpaperInputConsumer != null;
- final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
+ final InputConsumerImpl navInputConsumer =
+ mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_NAVIGATION);
+ final InputConsumerImpl pipInputConsumer =
+ mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_PIP);
+ final InputConsumerImpl wallpaperInputConsumer =
+ mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_WALLPAPER);
+ boolean addInputConsumerHandle = navInputConsumer != null;
+ boolean addPipInputConsumerHandle = pipInputConsumer != null;
+ boolean addWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
+ final Rect pipTouchableBounds = addPipInputConsumerHandle ? new Rect() : null;
boolean disableWallpaperTouchEvents = false;
final int count = mChildren.size();
@@ -490,9 +770,20 @@
// Skip this window because it cannot possibly receive input.
continue;
}
+
+ if (addPipInputConsumerHandle
+ && child.getStackId() == PINNED_STACK_ID
+ && inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
+ // Update the bounds of the Pip input consumer to match the Pinned stack
+ child.getStack().getBounds(pipTouchableBounds);
+ pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
+ inputMonitor.addInputWindowHandle(pipInputConsumer.mWindowHandle);
+ addPipInputConsumerHandle = false;
+ }
+
if (addInputConsumerHandle
- && inputWindowHandle.layer <= mService.mInputConsumer.mWindowHandle.layer) {
- inputMonitor.addInputWindowHandle(mService.mInputConsumer.mWindowHandle);
+ && inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
+ inputMonitor.addInputWindowHandle(navInputConsumer.mWindowHandle);
addInputConsumerHandle = false;
}
@@ -500,8 +791,7 @@
if (child.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER &&
child.isVisibleLw()) {
// Add the wallpaper input consumer above the first visible wallpaper.
- inputMonitor.addInputWindowHandle(
- mService.mWallpaperInputConsumer.mWindowHandle);
+ inputMonitor.addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
addWallpaperInputConsumerHandle = false;
}
}
@@ -512,19 +802,16 @@
final boolean hasFocus = child == inputFocus;
final boolean isVisible = child.isVisibleLw();
- if ((privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS)
- != 0) {
+ if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
disableWallpaperTouchEvents = true;
}
- final boolean hasWallpaper = wallpaperController.isWallpaperTarget(child)
- && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0
+ final boolean hasWallpaper = dc.mWallpaperController.isWallpaperTarget(child)
+ && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
&& !disableWallpaperTouchEvents;
- final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY);
// If there's a drag in progress and 'child' is a potential drop target,
// make sure it's been told about the drag
- if (inDrag && isVisible && onDefaultDisplay) {
+ if (inDrag && isVisible && dc.isDefaultDisplay) {
mService.mDragState.sendDragStartedIfNeededLw(child);
}
@@ -535,7 +822,7 @@
if (addWallpaperInputConsumerHandle) {
// No visible wallpaper found, add the wallpaper input consumer at the end.
- inputMonitor.addInputWindowHandle(mService.mWallpaperInputConsumer.mWindowHandle);
+ inputMonitor.addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
}
}
@@ -691,7 +978,7 @@
">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
mService.openSurfaceTransaction();
try {
- mService.mRoot.applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);
+ applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
@@ -700,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);
@@ -753,7 +1039,7 @@
}
}
- if (layoutNeeded()) {
+ if (isLayoutNeeded()) {
defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("mLayoutNeeded",
defaultDisplay.pendingLayoutChanges);
@@ -795,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();
@@ -812,7 +1098,7 @@
if (!token.hasVisible) {
exitingTokens.remove(i);
if (token.windowType == TYPE_WALLPAPER) {
- mService.mWallpaperControllerLocked.removeWallpaperToken(token);
+ displayContent.mWallpaperController.removeWallpaperToken(token);
}
}
}
@@ -840,13 +1126,13 @@
if (wallpaperDestroyed) {
defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- defaultDisplay.layoutNeeded = true;
+ defaultDisplay.setLayoutNeeded();
}
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.pendingLayoutChanges != 0) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -874,7 +1160,7 @@
if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
mService.mPowerManagerInternal.powerHint(
- mService.mPowerManagerInternal.POWER_HINT_SUSTAINED_PERFORMANCE_MODE,
+ PowerHint.SUSTAINED_PERFORMANCE,
(mSustainedPerformanceModeEnabled ? 1 : 0));
}
@@ -893,16 +1179,18 @@
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;
}
}
if (mService.mWaitingForDrawnCallback != null ||
- (mOrientationChangeComplete && !defaultDisplay.layoutNeeded &&
- !mUpdateRotation)) {
+ (mOrientationChangeComplete && !defaultDisplay.isLayoutNeeded()
+ && !mUpdateRotation)) {
mService.checkDrawnWindowsLocked();
}
@@ -913,7 +1201,7 @@
}
mService.mPendingRemove.toArray(mService.mPendingRemoveTmp);
mService.mPendingRemove.clear();
- DisplayContentList displayList = new DisplayContentList();
+ ArrayList<DisplayContent> displayList = new ArrayList();
for (i = 0; i < N; i++) {
final WindowState w = mService.mPendingRemoveTmp[i];
w.removeImmediately();
@@ -923,9 +1211,9 @@
}
}
- for (DisplayContent displayContent : displayList) {
- mService.mLayersController.assignLayersLocked(displayContent.getWindowList());
- displayContent.layoutNeeded = true;
+ for (int j = displayList.size() - 1; j >= 0; --j) {
+ final DisplayContent dc = displayList.get(j);
+ dc.assignWindowLayers(true /*setLayoutNeeded*/);
}
}
@@ -951,9 +1239,9 @@
}
// TODO: Super crazy long method that should be broken down...
- void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw, int defaultDh) {
+ private void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw, int defaultDh) {
mHoldScreenWindow = null;
- mObsuringWindow = null;
+ mObscuringWindow = null;
if (mService.mWatermark != null) {
mService.mWatermark.positionSurface(defaultDw, defaultDh);
@@ -974,49 +1262,47 @@
final int count = mChildren.size();
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
- boolean updateAllDrawn = false;
WindowList windows = dc.getWindowList();
DisplayInfo displayInfo = dc.getDisplayInfo();
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.
mDisplayHasContent = false;
mPreferredRefreshRate = 0;
mPreferredModeId = 0;
+ mTmpUpdateAllDrawn.clear();
int repeats = 0;
do {
repeats++;
if (repeats > 6) {
Slog.w(TAG, "Animation repeat aborted after too many iterations");
- dc.layoutNeeded = false;
+ dc.clearLayoutNeeded();
break;
}
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.layoutNeeded = true;
+ 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)) {
- dc.layoutNeeded = true;
- mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
+ if (mService.updateOrientationFromAppTokensLocked(true, displayId)) {
+ dc.setLayoutNeeded();
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
}
}
if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
- dc.layoutNeeded = true;
+ dc.setLayoutNeeded();
}
// FIRST LOOP: Perform a layout, if needed.
@@ -1068,47 +1354,16 @@
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 wallaper's visibility has been updated accordingly.
- mService.mWallpaperControllerLocked.updateWallpaperVisibility();
+ // current wallpaper's visibility has been updated accordingly.
+ dc.mWallpaperController.updateWallpaperVisibility();
}
+ w.handleWindowMovedIfNeeded();
+
final WindowStateAnimator winAnimator = w.mWinAnimator;
- // If the window has moved due to its containing content frame changing, then
- // notify the listeners and optionally animate it. Simply checking a change of
- // position is not enough, because being move due to dock divider is not a trigger
- // for animation.
- if (w.hasMoved()) {
- // Frame has moved, containing content frame has also moved, and we're not
- // currently animating... let's do something.
- final int left = w.mFrame.left;
- final int top = w.mFrame.top;
- final boolean adjustedForMinimizedDockOrIme = task != null
- && (task.mStack.isAdjustedForMinimizedDockedStack()
- || task.mStack.isAdjustedForIme());
- if (mService.okToDisplay()
- && (w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
- && !w.isDragResizing() && !adjustedForMinimizedDockOrIme
- && (task == null || w.getTask().mStack.hasMovementAnimations())
- && !w.mWinAnimator.mLastHidden) {
- winAnimator.setMoveAnimation(left, top);
- }
-
- //TODO (multidisplay): Accessibility supported only for the default display.
- if (mService.mAccessibilityController != null
- && displayId == Display.DEFAULT_DISPLAY) {
- mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
- }
-
- try {
- w.mClient.moved(left, top);
- } catch (RemoteException e) {
- }
- w.mMovedByResize = false;
- }
-
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
w.mContentChanged = false;
@@ -1155,71 +1410,10 @@
}
final AppWindowToken atoken = w.mAppToken;
- if (DEBUG_STARTING_WINDOW && atoken != null && w == atoken.startingWindow) {
- Slog.d(TAG, "updateWindows: starting " + w
- + " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
- + " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
- }
- if (atoken != null && (!atoken.allDrawn || !atoken.allDrawnExcludingSaved
- || atoken.mAppAnimator.freezingScreen)) {
- if (atoken.lastTransactionSequence != mService.mTransactionSequence) {
- atoken.lastTransactionSequence = mService.mTransactionSequence;
- atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
- atoken.numInterestingWindowsExcludingSaved = 0;
- atoken.numDrawnWindowsExcludingSaved = 0;
- atoken.startingDisplayed = false;
- }
- if (!atoken.allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) {
- if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
- Slog.v(TAG, "Eval win " + w + ": isDrawn="
- + w.isDrawnLw()
- + ", isAnimationSet=" + winAnimator.isAnimationSet());
- if (!w.isDrawnLw()) {
- Slog.v(TAG, "Not displayed: s="
- + winAnimator.mSurfaceController
- + " pv=" + w.mPolicyVisibility
- + " mDrawState=" + winAnimator.drawStateToString()
- + " ph=" + w.isParentWindowHidden()
- + " th=" + atoken.hiddenRequested
- + " a=" + winAnimator.mAnimating);
- }
- }
- if (w != atoken.startingWindow) {
- if (w.isInteresting()) {
- atoken.numInterestingWindows++;
- if (w.isDrawnLw()) {
- atoken.numDrawnWindows++;
- if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
- Slog.v(TAG, "tokenMayBeDrawn: " + atoken
- + " w=" + w + " numInteresting="
- + atoken.numInterestingWindows
- + " freezingScreen="
- + atoken.mAppAnimator.freezingScreen
- + " mAppFreezing=" + w.mAppFreezing);
- updateAllDrawn = true;
- }
- }
- } else if (w.isDrawnLw()) {
- mService.mH.sendEmptyMessage(NOTIFY_STARTING_WINDOW_DRAWN);
- atoken.startingDisplayed = true;
- }
- }
- if (!atoken.allDrawnExcludingSaved
- && w.mightAffectAllDrawn(true /* visibleOnly */)) {
- if (w != atoken.startingWindow && w.isInteresting()) {
- atoken.numInterestingWindowsExcludingSaved++;
- if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) {
- atoken.numDrawnWindowsExcludingSaved++;
- if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
- Slog.v(TAG, "tokenMayBeDrawnExcludingSaved: " + atoken
- + " w=" + w + " numInteresting="
- + atoken.numInterestingWindowsExcludingSaved
- + " freezingScreen="
- + atoken.mAppAnimator.freezingScreen
- + " mAppFreezing=" + w.mAppFreezing);
- updateAllDrawn = true;
- }
- }
+ if (atoken != null) {
+ final boolean updateAllDrawn = atoken.updateDrawnWindowStates(w);
+ if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(atoken)) {
+ mTmpUpdateAllDrawn.add(atoken);
}
}
@@ -1228,7 +1422,7 @@
focusDisplayed = true;
}
- mService.updateResizingWindows(w);
+ w.updateResizingWindowIfNeeded();
}
mService.mDisplayManagerInternal.setDisplayProperties(displayId,
@@ -1239,10 +1433,11 @@
dc.stopDimmingIfNeeded();
- if (updateAllDrawn) {
+ while (!mTmpUpdateAllDrawn.isEmpty()) {
+ final AppWindowToken atoken = mTmpUpdateAllDrawn.removeLast();
// See if any windows have been drawn, so they (and others associated with them)
// can now be shown.
- dc.updateAllDrawn();
+ atoken.updateAllDrawn(dc);
}
}
@@ -1270,7 +1465,7 @@
// so we want to leave all of them as undimmed (for
// performance reasons).
if (!mObscured) {
- mObsuringWindow = w;
+ mObscuringWindow = w;
}
mObscured = true;
@@ -1407,14 +1602,14 @@
}
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
- if (!layoutNeeded()) {
+ if (!isLayoutNeeded()) {
return;
}
- pw.print(" layoutNeeded on displays=");
+ pw.print(" mLayoutNeeded on displays=");
final int count = mChildren.size();
for (int displayNdx = 0; displayNdx < count; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
- if (displayContent.layoutNeeded) {
+ if (displayContent.isLayoutNeeded()) {
pw.print(displayContent.getDisplayId());
}
}
@@ -1435,6 +1630,13 @@
}
}
+ void dumpTokens(PrintWriter pw, boolean dumpAll) {
+ pw.println(" All tokens:");
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).dumpTokens(pw, dumpAll);
+ }
+ }
+
@Override
String getName() {
return "ROOT";
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 9e8c609..7f543f9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -20,7 +20,6 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -35,7 +34,6 @@
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.SurfaceControl;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -43,17 +41,17 @@
class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// Return value from {@link setBounds} indicating no change was made to the Task bounds.
- static final int BOUNDS_CHANGE_NONE = 0;
+ private static final int BOUNDS_CHANGE_NONE = 0;
// Return value from {@link setBounds} indicating the position of the Task bounds changed.
- static final int BOUNDS_CHANGE_POSITION = 1;
+ private static final int BOUNDS_CHANGE_POSITION = 1;
// Return value from {@link setBounds} indicating the size of the Task bounds changed.
- static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+ private static final int BOUNDS_CHANGE_SIZE = 1 << 1;
// TODO: Track parent marks like this in WindowContainer.
TaskStack mStack;
final int mTaskId;
final int mUserId;
- boolean mDeferRemoval = false;
+ private boolean mDeferRemoval = false;
final WindowManagerService mService;
// Content limits relative to the DisplayContent this sits in.
@@ -65,17 +63,11 @@
private final Rect mTempInsetBounds = new Rect();
// Device rotation as of the last time {@link #mBounds} was set.
- int mRotation;
+ private int mRotation;
// Whether mBounds is fullscreen
private boolean mFillsParent = true;
- /**
- * Contains configurations settings that are different from the parent configuration due to
- * stack specific operations. E.g. {@link #setBounds}.
- */
- Configuration mOverrideConfig = Configuration.EMPTY;
-
// For comparison with DisplayContent bounds.
private Rect mTmpRect = new Rect();
// For handling display rotations.
@@ -120,8 +112,9 @@
}
}
- if (wtoken.mParent != null) {
- wtoken.mParent.removeChild(wtoken);
+ final WindowContainer parent = wtoken.getParent();
+ if (parent != null) {
+ parent.removeChild(wtoken);
}
addChild(wtoken, addPos);
wtoken.mTask = this;
@@ -153,7 +146,7 @@
if (content != null) {
content.mDimLayerController.removeDimLayerUser(this);
}
- mParent.removeChild(this);
+ getParent().removeChild(this);
mService.mTaskIdToTask.delete(mTaskId);
}
@@ -165,11 +158,12 @@
if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
- mParent.removeChild(this);
+ getParent().removeChild(this);
stack.addTask(this, toTop);
}
- void positionTaskInStack(TaskStack stack, int position, Rect bounds, Configuration config) {
+ void positionTaskInStack(TaskStack stack, int position, Rect bounds,
+ Configuration overrideConfig) {
if (mStack != null && stack != mStack) {
if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
@@ -177,7 +171,7 @@
mStack.removeChild(this);
}
stack.positionTask(this, position, showForAllUsers());
- resizeLocked(bounds, config, false /* force */);
+ resizeLocked(bounds, overrideConfig, false /* force */);
for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
mChildren.get(activityNdx).notifyMovedInStack();
@@ -254,7 +248,7 @@
if (displayContent != null) {
displayContent.mDimLayerController.updateDimLayer(this);
}
- mOverrideConfig = mFillsParent ? Configuration.EMPTY : overrideConfig;
+ onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
return boundsChange;
}
@@ -283,8 +277,7 @@
}
boolean isResizeable() {
- return !mHomeTask
- && (ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks);
+ return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks;
}
boolean isOnTopLauncher() {
@@ -292,7 +285,7 @@
}
boolean cropWindowsToStackBounds() {
- return !mHomeTask && (isResizeable() || mResizeMode == RESIZE_MODE_CROP_WINDOWS);
+ return isResizeable();
}
boolean isHomeTask() {
@@ -321,8 +314,7 @@
*/
void prepareFreezingBounds() {
mPreparedFrozenBounds.set(mBounds);
- mPreparedFrozenMergedConfig.setTo(mService.mGlobalConfiguration);
- mPreparedFrozenMergedConfig.updateFrom(mOverrideConfig);
+ mPreparedFrozenMergedConfig.setTo(getConfiguration());
}
/**
@@ -334,9 +326,10 @@
* bounds's bottom; false if the task's top should be aligned
* the adjusted bounds's top.
*/
- void alignToAdjustedBounds(
- Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
- if (!isResizeable() || mOverrideConfig == Configuration.EMPTY) {
+ void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
+ // 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;
}
@@ -348,7 +341,7 @@
mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
}
setTempInsetBounds(tempInsetBounds);
- resizeLocked(mTmpRect2, mOverrideConfig, false /* forced */);
+ resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
@@ -500,12 +493,12 @@
mTmpRect2.set(mBounds);
if (!StackId.isTaskResizeAllowed(mStack.mStackId)) {
- setBounds(mTmpRect2, mOverrideConfig);
+ setBounds(mTmpRect2, getOverrideConfiguration());
return;
}
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
- if (setBounds(mTmpRect2, mOverrideConfig) != BOUNDS_CHANGE_NONE) {
+ if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
// Post message to inform activity manager of the bounds change simulating a one-way
// call. We do this to prevent a deadlock between window manager lock and activity
// manager lock been held.
@@ -567,7 +560,7 @@
@Override
public boolean dimFullscreen() {
- return isHomeTask() || isFullscreen();
+ return isFullscreen();
}
boolean isFullscreen() {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 21db840..6887312 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -478,7 +478,7 @@
private int getDimSide(int x) {
if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
|| !mTask.mStack.fillsParent()
- || mService.mGlobalConfiguration.orientation != ORIENTATION_LANDSCAPE) {
+ || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
return CTRL_NONE;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index e374185e..5637e51 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -24,19 +24,25 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
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.app.IActivityManager;
import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
@@ -51,6 +57,7 @@
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
+import com.android.internal.policy.PipSnapAlgorithm;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -94,16 +101,16 @@
private boolean mFillsParent = true;
// Device rotation as of the last time {@link #mBounds} was set.
- int mRotation;
+ private int mRotation;
/** Density as of last time {@link #mBounds} was set. */
- int mDensity;
+ private int mDensity;
/** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
- DimLayer mAnimationBackgroundSurface;
+ private DimLayer mAnimationBackgroundSurface;
/** The particular window with an Animation with non-zero background color. */
- WindowStateAnimator mAnimationBackgroundAnimator;
+ private WindowStateAnimator mAnimationBackgroundAnimator;
/** Application tokens that are exiting, but still on screen for animations. */
final AppTokenList mExitingAppTokens = new AppTokenList();
@@ -230,7 +237,7 @@
}
}
alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds);
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
@@ -354,11 +361,8 @@
// If the rotation or density didn't match, we'll update it in onConfigurationChanged.
}
- boolean onConfigurationChanged() {
- return updateBoundsAfterConfigChange();
- }
-
- private boolean updateBoundsAfterConfigChange() {
+ /** @return true if bounds were updated to some non-empty value. */
+ boolean updateBoundsAfterConfigChange() {
if (mDisplayContent == null) {
// If the stack is already detached we're not updating anything,
// as it's going away soon anyway.
@@ -381,19 +385,41 @@
mTmpRect2.set(mBounds);
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
- if (mStackId == DOCKED_STACK_ID) {
- repositionDockedStackAfterRotation(mTmpRect2);
- snapDockedStackAfterRotation(mTmpRect2);
- final int newDockSide = getDockSide(mTmpRect2);
+ switch (mStackId) {
+ case PINNED_STACK_ID:
+ // Keep the pinned stack in the same aspect ratio as in the old orientation, but
+ // move it into the position in the rotated space, and snap to the closest space
+ // in the new orientation.
- // Update the dock create mode and clear the dock create bounds, these
- // might change after a rotation and the original values will be invalid.
- mService.setDockedStackCreateStateLocked(
- (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
- null);
- mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
+ try {
+ final IActivityManager am = mService.mActivityManager;
+ final Rect movementBounds = am.getPictureInPictureMovementBounds(
+ mDisplayContent.getDisplayId());
+ final int width = mBounds.width();
+ final int height = mBounds.height();
+ final int left = mTmpRect2.centerX() - (width / 2);
+ final int top = mTmpRect2.centerY() - (height / 2);
+ mTmpRect2.set(left, top, left + width, top + height);
+
+ final PipSnapAlgorithm snapAlgorithm = new PipSnapAlgorithm(mService.mContext,
+ mDisplayContent.getDisplayId());
+ mTmpRect2.set(snapAlgorithm.findClosestSnapBounds(movementBounds, mTmpRect2));
+ } catch (RemoteException e) {}
+ break;
+ case DOCKED_STACK_ID:
+ repositionDockedStackAfterRotation(mTmpRect2);
+ snapDockedStackAfterRotation(mTmpRect2);
+ final int newDockSide = getDockSide(mTmpRect2);
+
+ // Update the dock create mode and clear the dock create bounds, these
+ // might change after a rotation and the original values will be invalid.
+ mService.setDockedStackCreateStateLocked(
+ (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
+ ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+ : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+ null);
+ mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
+ break;
}
mBoundsAfterRotation.set(mTmpRect2);
@@ -449,8 +475,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);
@@ -459,7 +484,7 @@
// Snap the position to a target.
final int rotation = displayInfo.rotation;
- final int orientation = mService.mGlobalConfiguration.orientation;
+ final int orientation = mDisplayContent.getConfiguration().orientation;
mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
mService.mContext.getResources(), displayWidth, displayHeight,
@@ -598,7 +623,7 @@
if (mChildren.isEmpty()) {
mDisplayContent.moveStack(this, false);
}
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) {
final AppWindowToken wtoken = mExitingAppTokens.get(appNdx);
@@ -609,12 +634,12 @@
}
}
- void attachDisplayContent(DisplayContent displayContent) {
+ void onDisplayChanged(DisplayContent dc) {
if (mDisplayContent != null) {
- throw new IllegalStateException("attachDisplayContent: Already attached");
+ throw new IllegalStateException("onDisplayChanged: Already attached");
}
- mDisplayContent = displayContent;
+ mDisplayContent = dc;
mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
"animation background stackId=" + mStackId);
@@ -628,7 +653,7 @@
// not fullscreen. If it's fullscreen, it means that we are in the transition of
// dismissing it, so we must not resize this stack.
bounds = new Rect();
- displayContent.getLogicalDisplayRect(mTmpRect);
+ dc.getLogicalDisplayRect(mTmpRect);
mTmpRect2.setEmpty();
if (dockedStack != null) {
dockedStack.getRawBounds(mTmpRect2);
@@ -641,6 +666,8 @@
}
updateDisplayInfo(bounds);
+
+ super.onDisplayChanged(dc);
}
void getStackDockedModeBoundsLocked(Rect outBounds, boolean ignoreVisibility) {
@@ -713,7 +740,7 @@
di.logicalWidth,
di.logicalHeight,
dockDividerWidth,
- mService.mGlobalConfiguration.orientation == ORIENTATION_PORTRAIT,
+ mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT,
mTmpRect2).getMiddleTarget().position;
if (dockOnTopOrLeft) {
@@ -782,13 +809,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 +830,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);
}
}
@@ -1009,7 +1037,7 @@
}
if (dockSide == DOCKED_TOP) {
- mService.getStableInsetsLocked(mTmpRect);
+ mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
int topInset = mTmpRect.top;
mTmpAdjustedBounds.set(mBounds);
mTmpAdjustedBounds.bottom =
@@ -1040,7 +1068,7 @@
}
if (dockSide == DOCKED_TOP) {
- mService.getStableInsetsLocked(mTmpRect);
+ mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
int topInset = mTmpRect.top;
return mBounds.bottom - topInset;
} else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
@@ -1080,7 +1108,7 @@
final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds;
task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP);
- mDisplayContent.layoutNeeded = true;
+ mDisplayContent.setLayoutNeeded();
}
boolean isAdjustedForMinimizedDockedStack() {
@@ -1182,7 +1210,7 @@
return DOCKED_INVALID;
}
mDisplayContent.getLogicalDisplayRect(mTmpRect);
- final int orientation = mService.mGlobalConfiguration.orientation;
+ final int orientation = mDisplayContent.getConfiguration().orientation;
return getDockSideUnchecked(bounds, mTmpRect, orientation);
}
@@ -1430,8 +1458,8 @@
return super.checkCompleteDeferredRemoval();
}
- void stepAppWindowsAnimation(long currentTime, int displayId) {
- super.stepAppWindowsAnimation(currentTime, displayId);
+ void stepAppWindowsAnimation(long currentTime) {
+ super.stepAppWindowsAnimation(currentTime);
// TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
// below but is set in the loop above. See if it really matters...
@@ -1439,14 +1467,13 @@
for (int i = 0; i < exitingCount; i++) {
final AppWindowAnimator appAnimator = mExitingAppTokens.get(i).mAppAnimator;
appAnimator.wasAnimating = appAnimator.animating;
- if (appAnimator.stepAnimationLocked(currentTime, displayId)) {
+ if (appAnimator.stepAnimationLocked(currentTime)) {
mService.mAnimator.setAnimating(true);
mService.mAnimator.mAppWindowAnimating = true;
} else if (appAnimator.wasAnimating) {
// stopped animating, do one more pass through the layout
- appAnimator.mAppToken.setAppLayoutChanges(
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "exiting appToken " + appAnimator.mAppToken + " done", displayId);
+ appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
+ "exiting appToken " + appAnimator.mAppToken + " done");
if (DEBUG_ANIM) Slog.v(TAG_WM,
"updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 58439eb..4ab8887 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -18,14 +18,10 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -39,6 +35,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.WindowManager;
@@ -149,18 +146,11 @@
}
void updateWallpaperVisibility() {
- final DisplayContent displayContent = mWallpaperTarget.getDisplayContent();
- if (displayContent == null) {
- return;
- }
final boolean visible = isWallpaperVisible(mWallpaperTarget);
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.updateWallpaperVisibility(dw, dh, visible, displayContent);
+ token.updateWallpaperVisibility(visible);
}
}
@@ -312,7 +302,7 @@
return null;
}
- void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
+ private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
final DisplayContent displayContent = changingTarget.getDisplayContent();
if (displayContent == null) {
return;
@@ -384,18 +374,6 @@
return mWallpaperAnimLayerAdjustment;
}
- void setAnimLayerAdjustment(WindowState win, int adj) {
- if (win != mWallpaperTarget || mLowerWallpaperTarget != null) {
- return;
- }
-
- if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "Setting wallpaper layer adj to " + adj);
- mWallpaperAnimLayerAdjustment = adj;
- for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
- mWallpaperTokens.get(i).adjustAnimLayer(adj);
- }
- }
-
private void findWallpaperTarget(WindowList windows, FindWallpaperTargetResult result) {
final WindowAnimator winAnimator = mService.mAnimator;
result.reset();
@@ -477,101 +455,111 @@
}
}
+ /** Updates the target wallpaper if needed and returns true if an update happened. */
private boolean updateWallpaperWindowsTarget(
WindowList windows, FindWallpaperTargetResult result) {
- boolean targetChanged = false;
WindowState wallpaperTarget = result.wallpaperTarget;
int wallpaperTargetIndex = result.wallpaperTargetIndex;
- if (mWallpaperTarget != wallpaperTarget
- && (mLowerWallpaperTarget == null || mLowerWallpaperTarget != wallpaperTarget)) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New wallpaper target: " + wallpaperTarget + " oldTarget: " + mWallpaperTarget);
+ if (mWallpaperTarget == wallpaperTarget
+ || (mLowerWallpaperTarget != null && mLowerWallpaperTarget == wallpaperTarget)) {
- mLowerWallpaperTarget = null;
- mUpperWallpaperTarget = null;
-
- WindowState oldW = mWallpaperTarget;
- mWallpaperTarget = wallpaperTarget;
- targetChanged = true;
-
- // Now what is happening... if the current and new targets are animating,
- // then we are in our super special mode!
- if (wallpaperTarget != null && oldW != null) {
- boolean oldAnim = oldW.isAnimatingLw();
- boolean foundAnim = wallpaperTarget.isAnimatingLw();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New animation: " + foundAnim + " old animation: " + oldAnim);
- if (foundAnim && oldAnim) {
- int oldI = windows.indexOf(oldW);
+ if (mLowerWallpaperTarget != null) {
+ // Is it time to stop animating?
+ if (!mLowerWallpaperTarget.isAnimatingLw()
+ || !mUpperWallpaperTarget.isAnimatingLw()) {
if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New i: " + wallpaperTargetIndex + " old i: " + oldI);
- if (oldI >= 0) {
- final boolean newTargetHidden =
- wallpaperTarget.mAppToken != null && wallpaperTarget.mAppToken.hiddenRequested;
- final boolean oldTargetHidden =
- oldW.mAppToken != null && oldW.mAppToken.hiddenRequested;
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:"
- + " old#" + oldI + "=" + oldW + " hidden=" + oldTargetHidden
- + " new#" + wallpaperTargetIndex + "=" + wallpaperTarget
- + " hidden=" + newTargetHidden);
-
- // Set the upper and lower wallpaper targets correctly,
- // and make sure that we are positioning the wallpaper below the lower.
- if (wallpaperTargetIndex > oldI) {
- // The new target is on top of the old one.
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Found target above old target.");
- mUpperWallpaperTarget = wallpaperTarget;
- mLowerWallpaperTarget = oldW;
-
- wallpaperTarget = oldW;
- wallpaperTargetIndex = oldI;
- } else {
- // The new target is below the old one.
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Found target below old target.");
- mUpperWallpaperTarget = oldW;
- mLowerWallpaperTarget = wallpaperTarget;
- }
- if (newTargetHidden && !oldTargetHidden) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Old wallpaper still the target.");
- // Use the old target if new target is hidden but old target
- // is not. If they're both hidden, still use the new target.
- mWallpaperTarget = oldW;
- } else if (newTargetHidden == oldTargetHidden
- && !mService.mOpeningApps.contains(wallpaperTarget.mAppToken)
- && (mService.mOpeningApps.contains(oldW.mAppToken)
- || mService.mClosingApps.contains(oldW.mAppToken))) {
- // If they're both hidden (or both not hidden), prefer the one that's
- // currently in opening or closing app list, this allows transition
- // selection logic to better determine the wallpaper status of
- // opening/closing apps.
- mWallpaperTarget = oldW;
- }
- }
+ "No longer animating wallpaper targets!");
+ mLowerWallpaperTarget = null;
+ mUpperWallpaperTarget = null;
+ mWallpaperTarget = wallpaperTarget;
+ return true;
}
}
- } else if (mLowerWallpaperTarget != null) {
- // Is it time to stop animating?
- if (!mLowerWallpaperTarget.isAnimatingLw() || !mUpperWallpaperTarget.isAnimatingLw()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
- mLowerWallpaperTarget = null;
- mUpperWallpaperTarget = null;
- mWallpaperTarget = wallpaperTarget;
- targetChanged = true;
- }
+ return false;
+ }
+
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+ "New wallpaper target: " + wallpaperTarget + " oldTarget: " + mWallpaperTarget);
+
+ mLowerWallpaperTarget = null;
+ mUpperWallpaperTarget = null;
+
+ WindowState oldW = mWallpaperTarget;
+ mWallpaperTarget = wallpaperTarget;
+
+ if (wallpaperTarget == null || oldW == null) {
+ return true;
+ }
+
+ // Now what is happening... if the current and new targets are animating,
+ // then we are in our super special mode!
+ boolean oldAnim = oldW.isAnimatingLw();
+ boolean foundAnim = wallpaperTarget.isAnimatingLw();
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+ "New animation: " + foundAnim + " old animation: " + oldAnim);
+
+ if (!foundAnim || !oldAnim) {
+ return true;
+ }
+
+ int oldI = windows.indexOf(oldW);
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+ "New i: " + wallpaperTargetIndex + " old i: " + oldI);
+
+ if (oldI < 0) {
+ return true;
+ }
+
+ final boolean newTargetHidden = wallpaperTarget.mAppToken != null
+ && wallpaperTarget.mAppToken.hiddenRequested;
+ final boolean oldTargetHidden = oldW.mAppToken != null
+ && oldW.mAppToken.hiddenRequested;
+
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old#" + oldI + "="
+ + oldW + " hidden=" + oldTargetHidden + " new#" + wallpaperTargetIndex + "="
+ + wallpaperTarget + " hidden=" + newTargetHidden);
+
+ // Set the upper and lower wallpaper targets correctly,
+ // and make sure that we are positioning the wallpaper below the lower.
+ if (wallpaperTargetIndex > oldI) {
+ // The new target is on top of the old one.
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Found target above old target.");
+ mUpperWallpaperTarget = wallpaperTarget;
+ mLowerWallpaperTarget = oldW;
+
+ wallpaperTarget = oldW;
+ wallpaperTargetIndex = oldI;
+ } else {
+ // The new target is below the old one.
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Found target below old target.");
+ mUpperWallpaperTarget = oldW;
+ mLowerWallpaperTarget = wallpaperTarget;
+ }
+
+ if (newTargetHidden && !oldTargetHidden) {
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+ // Use the old target if new target is hidden but old target
+ // is not. If they're both hidden, still use the new target.
+ mWallpaperTarget = oldW;
+ } else if (newTargetHidden == oldTargetHidden
+ && !mService.mOpeningApps.contains(wallpaperTarget.mAppToken)
+ && (mService.mOpeningApps.contains(oldW.mAppToken)
+ || mService.mClosingApps.contains(oldW.mAppToken))) {
+ // If they're both hidden (or both not hidden), prefer the one that's currently in
+ // opening or closing app list, this allows transition selection logic to better
+ // determine the wallpaper status of opening/closing apps.
+ mWallpaperTarget = oldW;
}
result.setWallpaperTarget(wallpaperTarget, wallpaperTargetIndex);
- return targetChanged;
+ return true;
}
- boolean updateWallpaperWindowsTargetByLayer(
- WindowList windows, FindWallpaperTargetResult result) {
+ private boolean updateWallpaperWindowsTargetByLayer(WindowList windows,
+ FindWallpaperTargetResult result) {
WindowState wallpaperTarget = result.wallpaperTarget;
int wallpaperTargetIndex = result.wallpaperTargetIndex;
@@ -621,7 +609,7 @@
return visible;
}
- boolean updateWallpaperWindowsPlacement(WindowList windows,
+ private boolean updateWallpaperWindowsPlacement(WindowList windows,
WindowState wallpaperTarget, int wallpaperTargetIndex, boolean visible) {
// TODO(multidisplay): Wallpapers on main screen only.
@@ -640,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);
@@ -734,6 +721,30 @@
return transitionReady;
}
+ /**
+ * 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) {
+ boolean adjust = false;
+ if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+ adjust = true;
+ } else {
+ for (int i = openingApps.size() - 1; i >= 0; --i) {
+ final AppWindowToken token = openingApps.valueAt(i);
+ if (token.windowsCanBeWallpaperTarget()) {
+ adjust = true;
+ break;
+ }
+ }
+ }
+
+ if (adjust) {
+ dc.adjustWallpaperWindows();
+ }
+ }
+
void addWallpaperToken(WindowToken token) {
mWallpaperTokens.add(token);
}
@@ -756,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 af0fbd3..db61c3e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.annotation.CallSuper;
+import android.content.res.Configuration;
import android.view.animation.Animation;
import java.util.Comparator;
@@ -34,13 +35,33 @@
*/
class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
- // The parent of this window container.
- protected WindowContainer mParent = null;
+ /**
+ * The parent of this window container.
+ * For removing or setting new parent {@link #setParent} should be used, because it also
+ * performs configuration updates based on new parent's settings.
+ */
+ private WindowContainer mParent = null;
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final LinkedList<E> mChildren = new LinkedList();
+ /** Contains override configuration settings applied to this window container. */
+ private Configuration mOverrideConfiguration = new Configuration();
+
+ /**
+ * Contains full configuration applied to this window container. Corresponds to full parent's
+ * config with applied {@link #mOverrideConfiguration}.
+ */
+ private Configuration mFullConfiguration = new Configuration();
+
+ /**
+ * Contains merged override configuration settings from the top of the hierarchy down to this
+ * particular instance. It is different from {@link #mFullConfiguration} because it starts from
+ * topmost container's override config instead of global config.
+ */
+ private Configuration mMergedOverrideConfiguration = new Configuration();
+
// The specified orientation for this window container.
protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -48,6 +69,18 @@
return mParent;
}
+ final protected void setParent(WindowContainer parent) {
+ mParent = parent;
+ // 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.
private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList();
private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList();
@@ -61,39 +94,43 @@
*/
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
- if (child.mParent != null) {
+ if (child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
- + " is already a child of container=" + child.mParent.getName()
+ + " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
- child.mParent = 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. */
@CallSuper
protected void addChild(E child, int index) {
- if (child.mParent != null) {
+ if (child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
- + " is already a child of container=" + child.mParent.getName()
+ + " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
- child.mParent = 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);
}
/**
@@ -104,7 +141,7 @@
@CallSuper
void removeChild(E child) {
if (mChildren.remove(child)) {
- child.mParent = null;
+ child.setParent(null);
} else {
throw new IllegalArgumentException("removeChild: container=" + child.getName()
+ " is not a child of container=" + getName());
@@ -158,6 +195,84 @@
return false;
}
+ /**
+ * Returns full configuration applied to this window container.
+ * This method should be used for getting settings applied in each particular level of the
+ * hierarchy.
+ */
+ Configuration getConfiguration() {
+ return mFullConfiguration;
+ }
+
+ /**
+ * Notify that parent config changed and we need to update full configuration.
+ * @see #mFullConfiguration
+ */
+ void onConfigurationChanged(Configuration newParentConfig) {
+ mFullConfiguration.setTo(newParentConfig);
+ mFullConfiguration.updateFrom(mOverrideConfiguration);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ child.onConfigurationChanged(mFullConfiguration);
+ }
+ }
+
+ /** Returns override configuration applied to this window container. */
+ Configuration getOverrideConfiguration() {
+ return mOverrideConfiguration;
+ }
+
+ /**
+ * Update override configuration and recalculate full config.
+ * @see #mOverrideConfiguration
+ * @see #mFullConfiguration
+ */
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ mOverrideConfiguration.setTo(overrideConfiguration);
+ // Update full configuration of this container and all its children.
+ onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY);
+ // Update merged override config of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
+
+ /**
+ * Get merged override configuration from the top of the hierarchy down to this
+ * particular instance. This should be reported to client as override config.
+ */
+ Configuration getMergedOverrideConfiguration() {
+ return mMergedOverrideConfiguration;
+ }
+
+ /**
+ * Update merged override configuration based on corresponding parent's config and notify all
+ * its children. If there is no parent, merged override configuration will set equal to current
+ * override config.
+ * @see #mMergedOverrideConfiguration
+ */
+ private void onMergedOverrideConfigurationChanged() {
+ if (mParent != null) {
+ mMergedOverrideConfiguration.setTo(mParent.getMergedOverrideConfiguration());
+ mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
+ } else {
+ mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
+ }
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ child.onMergedOverrideConfigurationChanged();
+ }
+ }
+
+ /**
+ * Notify that the display this container is on has changed.
+ * @param dc The new display this container is on.
+ */
+ void onDisplayChanged(DisplayContent dc) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ child.onDisplayChanged(dc);
+ }
+ }
+
void setWaitingForDrawnIfResizingChanged() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
@@ -276,35 +391,18 @@
}
/** Checks if all windows in an app are all drawn and shows them if needed. */
- // TODO: The displayId shouldn't be needed as there shouldn't be a container on more than one
- // display. Remove once we migrate DisplayContent to use WindowContainer.
- void checkAppWindowsReadyToShow(int displayId) {
+ void checkAppWindowsReadyToShow() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- wc.checkAppWindowsReadyToShow(displayId);
- }
- }
-
- /**
- * Updates the current all drawn status for this container. That is all its children
- * that should draw something have done so.
- */
- // TODO: The displayId shouldn't be needed as there shouldn't be a container on more than one
- // display. Remove once we migrate DisplayContent to use WindowContainer.
- void updateAllDrawn(int displayId) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- wc.updateAllDrawn(displayId);
+ wc.checkAppWindowsReadyToShow();
}
}
/** Step currently ongoing animation for App window containers. */
- // TODO: The displayId shouldn't be needed as there shouldn't be a container on more than one
- // display. Remove once we migrate DisplayContent to use WindowContainer.
- void stepAppWindowsAnimation(long currentTime, int displayId) {
+ void stepAppWindowsAnimation(long currentTime) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- wc.stepAppWindowsAnimation(currentTime, displayId);
+ wc.stepAppWindowsAnimation(currentTime);
}
}
@@ -389,16 +487,15 @@
/**
* Rebuilds the WindowList for the input display content.
- * @param dc The display content to rebuild the window list for.
* @param addIndex The index in the window list to add the next entry to.
* @return The next index in the window list to.
*/
// TODO: Hoping we can get rid of WindowList so this method wouldn't be needed.
- int rebuildWindowList(DisplayContent dc, int addIndex) {
+ int rebuildWindowList(int addIndex) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final WindowContainer wc = mChildren.get(i);
- addIndex = wc.rebuildWindowList(dc, addIndex);
+ addIndex = wc.rebuildWindowList(addIndex);
}
return addIndex;
}
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 1ed4055..1b08f16 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -163,15 +163,16 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
+import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
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;
@@ -203,6 +205,7 @@
import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.EventLogTags.WM_TASK_CREATED;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
@@ -213,7 +216,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
@@ -222,12 +224,10 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
@@ -404,11 +404,6 @@
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
/**
- * Mapping from a token IBinder to a WindowToken object.
- */
- final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();
-
- /**
* List of window tokens that have finished starting their application,
* and now need to have the policy remove their windows.
*/
@@ -428,17 +423,6 @@
final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
/**
- * The input consumer added to the window manager which consumes input events to windows below
- * it.
- */
- InputConsumerImpl mInputConsumer;
-
- /**
- * The input consumer added to the window manager before all wallpaper windows.
- */
- InputConsumerImpl mWallpaperInputConsumer;
-
- /**
* Windows that are being resized. Used so we can tell the client about
* the resize after closing the transaction in which we resized the
* underlying surface.
@@ -532,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;
@@ -547,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 {
@@ -579,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;
@@ -592,12 +575,6 @@
// State while inside of layoutAndPlaceSurfacesLocked().
boolean mFocusMayChange;
- /**
- * Current global configuration information. Contains general settings for the entire system,
- * corresponds to the configuration of the default display.
- */
- Configuration mGlobalConfiguration = new Configuration();
-
// This is held as long as we have the screen frozen, to give us time to
// perform a rotation animation when turning off shows the lock screen which
// changes the orientation.
@@ -631,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
@@ -641,6 +628,12 @@
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
SettingsObserver mSettingsObserver;
+ // A count of the windows which are 'seamlessly rotated', e.g. a surface
+ // at an old orientation is being transformed. We freeze orientation updates
+ // while any windows are seamlessly rotated, so we need to track when this
+ // hits zero so we can apply deferred orientation updates.
+ int mSeamlessRotationCount = 0;
+
private final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
@@ -691,10 +684,6 @@
}
}
- WallpaperController mWallpaperControllerLocked;
-
- final WindowLayersController mLayersController;
-
boolean mAnimateWallpaperWithTarget;
// TODO: Move to RootWindowContainer
@@ -703,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;
@@ -726,7 +715,7 @@
DragState mDragState = null;
// For frozen screen animations.
- int mExitAnimId, mEnterAnimId;
+ private int mExitAnimId, mEnterAnimId;
boolean mAnimationScheduled;
@@ -913,7 +902,7 @@
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED);
- AppWindowToken atoken = findAppWindowToken(token);
+ final AppWindowToken atoken = mRoot.getAppWindowToken(token);
if (atoken == null) {
return;
}
@@ -988,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);
@@ -1103,367 +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 = curTarget.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.
- WindowState dockedDivider = w.mDisplayContent.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;
- }
-
- void addInputMethodWindowToListLocked(WindowState win) {
- int pos = findDesiredInputMethodWindowIndexLocked(true);
- if (pos >= 0) {
- if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
- TAG_WM, "Adding input method window " + win + " at " + pos);
- // TODO(multidisplay): IMEs are only supported on the default display.
- getDefaultWindowListLocked().add(pos, win);
- mWindowsChanged = true;
- moveInputMethodDialogsLocked(pos + 1);
- return;
- }
- win.mToken.addWindow(win);
- moveInputMethodDialogsLocked(pos);
- }
-
- 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);
- }
- }
-
- 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:
@@ -1536,7 +1167,8 @@
final boolean hasParent = parentWindow != null;
// Use existing parent window token for child windows since they go in the same token
// as there parent window so we can apply the same policy on them.
- WindowToken token = mTokenMap.get(hasParent ? parentWindow.mAttrs.token : attrs.token);
+ WindowToken token = mRoot.getWindowToken(
+ hasParent ? parentWindow.mAttrs.token : attrs.token, displayContent);
// If this is a child window, we want to apply the same type checking rules as the
// parent window type.
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
@@ -1588,7 +1220,7 @@
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
- token = new WindowToken(this, attrs.token, -1, false);
+ token = new WindowToken(this, attrs.token, -1, false, displayContent);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
if (atoken == null) {
@@ -1656,11 +1288,11 @@
// It is not valid to use an app token with other system types; we will
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
- token = new WindowToken(this, null, -1, false);
+ token = new WindowToken(this, null, -1, false, displayContent);
}
WindowState win = new WindowState(this, session, client, token, parentWindow,
- appOp[0], seq, attrs, viewVisibility, displayContent, session.mUid);
+ appOp[0], seq, attrs, viewVisibility, session.mUid);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
@@ -1751,21 +1383,22 @@
if (type == TYPE_INPUT_METHOD) {
win.mGivenInsetsPending = true;
mInputMethodWindow = win;
- addInputMethodWindowToListLocked(win);
+ win.mToken.addImeWindow(win);
imMayMove = false;
} 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.
@@ -1780,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;
@@ -1832,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*/);
@@ -1847,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);
@@ -1985,7 +1618,7 @@
}
}
- public void removeWindow(Session session, IWindow client) {
+ void removeWindow(Session session, IWindow client) {
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
@@ -2026,7 +1659,7 @@
// Window will already be removed from token before this post clean-up method is called.
if (token.isEmpty()) {
if (!token.explicit) {
- mTokenMap.remove(token.token);
+ token.removeImmediately();
} else if (atoken != null) {
// TODO: Should this be moved into AppWindowToken.removeWindow? Might go away after
// re-factor.
@@ -2039,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();
@@ -2062,7 +1693,7 @@
mInputMonitor.updateInputWindowsLw(true /*force*/);
}
- public void updateAppOpsState() {
+ private void updateAppOpsState() {
synchronized(mWindowMap) {
mRoot.updateAppOpsState();
}
@@ -2156,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);
}
}
@@ -2250,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) {
@@ -2423,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) {
@@ -2488,7 +2123,7 @@
}
if (configChanged) {
- sendNewConfiguration();
+ sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
return result;
@@ -2511,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.
@@ -2523,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;
@@ -2645,7 +2279,7 @@
}
}
- public void finishDrawingWindow(Session session, IWindow client) {
+ void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
@@ -2654,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();
@@ -2674,7 +2308,8 @@
// is running.
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
if (okToDisplay()) {
- DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+ final DisplayContent displayContent = atoken.mTask.getDisplayContent();
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int width = displayInfo.appWidth;
final int height = displayInfo.appHeight;
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
@@ -2711,10 +2346,10 @@
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+ " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
- Animation a = mAppTransition.loadAnimation(lp, transit, enter,
- mGlobalConfiguration.uiMode, mGlobalConfiguration.orientation, frame,
- displayFrame, insets, surfaceInsets, isVoiceInteraction, freeform,
- atoken.mTask.mTaskId);
+ final Configuration displayConfig = displayContent.getConfiguration();
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
+ displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
+ isVoiceInteraction, freeform, atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
final int containingWidth = frame.width();
@@ -2740,10 +2375,8 @@
== PackageManager.PERMISSION_GRANTED) {
return true;
}
- String msg = "Permission Denial: " + func + " from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + permission;
+ final String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid() + " requires " + permission;
Slog.w(TAG_WM, msg);
return false;
}
@@ -2752,56 +2385,46 @@
return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOn();
}
- AppWindowToken findAppWindowToken(IBinder token) {
- WindowToken wtoken = mTokenMap.get(token);
- if (wtoken == null) {
- return null;
- }
- return wtoken.asAppWindowToken();
- }
-
@Override
public void addWindowToken(IBinder token, int type) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "addWindowToken()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
- WindowToken wtoken = mTokenMap.get(token);
- if (wtoken != null) {
- Slog.w(TAG_WM, "Attempted to add existing input method token: " + token);
- return;
- }
- wtoken = new WindowToken(this, token, type, true);
- if (type == TYPE_WALLPAPER) {
- mWallpaperControllerLocked.addWallpaperToken(wtoken);
- }
+ mRoot.addWindowToken(token, type);
}
}
@Override
public void removeWindowToken(IBinder token) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "removeWindowToken()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
final long origId = Binder.clearCallingIdentity();
- synchronized(mWindowMap) {
- final WindowToken wtoken = mTokenMap.remove(token);
- if (wtoken != null) {
- wtoken.setExiting();
- if (wtoken.windowType == TYPE_WALLPAPER) {
- mWallpaperControllerLocked.removeWallpaperToken(wtoken);
+ try {
+ synchronized (mWindowMap) {
+ final ArrayList<WindowToken> removedTokens = mRoot.removeWindowToken(token);
+ if (removedTokens == null || removedTokens.isEmpty()) {
+ Slog.w(TAG_WM,
+ "removeWindowToken: Attempted to remove non-existing token: " + token);
+ return;
}
- mInputMonitor.updateInputWindowsLw(true /*force*/);
- } else {
- Slog.w(TAG_WM, "Attempted to remove non-existing token: " + token);
+ for (int i = removedTokens.size() - 1; i >= 0; --i) {
+ final WindowToken wtoken = removedTokens.get(i);
+ wtoken.setExiting();
+ if (wtoken.windowType == TYPE_WALLPAPER) {
+ wtoken.getDisplayContent().mWallpaperController.removeWallpaperToken(wtoken);
+ }
+
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- Binder.restoreCallingIdentity(origId);
}
private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken,
@@ -2810,9 +2433,9 @@
+ " atoken=" + atoken + " bounds=" + bounds);
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack == null) {
- throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
+ throw new IllegalArgumentException("createTaskLocked: invalid stackId=" + stackId);
}
- EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
+ EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
Task task = new Task(taskId, stack, userId, this, bounds, overrideConfig, isOnTopLauncher);
mTaskIdToTask.put(taskId, task);
stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers);
@@ -2826,8 +2449,7 @@
Rect taskBounds, Configuration overrideConfig, int taskResizeMode,
boolean alwaysFocusable, boolean homeTask, int targetSdkVersion,
int rotationAnimationHint, boolean isOnTopLauncher) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "addAppToken()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -2846,12 +2468,18 @@
}
synchronized(mWindowMap) {
- AppWindowToken atoken = findAppWindowToken(token.asBinder());
+ AppWindowToken atoken = mRoot.getAppWindowToken(token.asBinder());
if (atoken != null) {
Slog.w(TAG_WM, "Attempted to add existing app token: " + token);
return;
}
- atoken = new AppWindowToken(this, token, voiceInteraction);
+
+ final TaskStack stack = mStackIdToStack.get(stackId);
+ if (stack == null) {
+ throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
+ }
+
+ atoken = new AppWindowToken(this, token, voiceInteraction, stack.getDisplayContent());
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
atoken.setFillsParent(fullscreen);
atoken.showForAllUsers = showForAllUsers;
@@ -2882,13 +2510,12 @@
public void setAppTask(IBinder token, int taskId, int stackId, Rect taskBounds,
Configuration overrideConfig, int taskResizeMode, boolean homeTask,
boolean isOnTopLauncher) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppTask()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppTask()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
- final AppWindowToken atoken = findAppWindowToken(token);
+ final AppWindowToken atoken = mRoot.getAppWindowToken(token);
if (atoken == null) {
Slog.w(TAG_WM, "Attempted to set task id of non-existing app token: " + token);
return;
@@ -2903,119 +2530,44 @@
}
}
- 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) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "updateOrientationFromAppTokens()")) {
+ 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 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) {
- final AppWindowToken atoken = findAppWindowToken(freezeThisOneIfNeeded);
+ 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) {
+ final AppWindowToken atoken = mRoot.getAppWindowToken(freezeThisOneIfNeeded);
if (atoken != null) {
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
@@ -3025,11 +2577,11 @@
// 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();
- displayContent.layoutNeeded = true;
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ displayContent.setLayoutNeeded();
int anim[] = new int[2];
if (displayContent.isDimming()) {
anim[0] = anim[1] = 0;
@@ -3044,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;
}
@@ -3093,9 +2643,8 @@
}
@Override
- public int[] setNewConfiguration(Configuration config) {
- if (!checkCallingPermission(android.Manifest.permission.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");
}
@@ -3104,11 +2653,7 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
- final boolean configChanged = mGlobalConfiguration.diff(config) != 0;
- if (!configChanged) {
- return null;
- }
- return mRoot.onConfigurationChanged(config);
+ return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId);
}
}
@@ -3124,13 +2669,12 @@
@Override
public void setAppOrientation(IApplicationToken token, int requestedOrientation) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppOrientation()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppOrientation()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
- final AppWindowToken atoken = findAppWindowToken(token.asBinder());
+ final AppWindowToken atoken = mRoot.getAppWindowToken(token.asBinder());
if (atoken == null) {
Slog.w(TAG_WM, "Attempted to set orientation of non-existing app token: " + token);
return;
@@ -3143,9 +2687,9 @@
@Override
public int getAppOrientation(IApplicationToken token) {
synchronized(mWindowMap) {
- AppWindowToken wtoken = findAppWindowToken(token.asBinder());
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token.asBinder());
if (wtoken == null) {
- return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ return SCREEN_ORIENTATION_UNSPECIFIED;
}
return wtoken.getOrientation();
@@ -3164,8 +2708,7 @@
@Override
public void setFocusedApp(IBinder token, boolean moveFocusNow) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setFocusedApp()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setFocusedApp()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -3175,7 +2718,7 @@
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, was " + mFocusedApp);
newFocus = null;
} else {
- newFocus = findAppWindowToken(token);
+ newFocus = mRoot.getAppWindowToken(token);
if (newFocus == null) {
Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token);
}
@@ -3206,8 +2749,7 @@
*/
@Override
public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "prepareAppTransition()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
@@ -3335,8 +2877,7 @@
@Override
public void executeAppTransition() {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "executeAppTransition()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "executeAppTransition()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -3360,8 +2901,7 @@
int theme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo,
int windowFlags, IBinder transferFrom, boolean createIfNeeded) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppStartingWindow()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppStartingWindow()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -3370,7 +2910,7 @@
TAG_WM, "setAppStartingWindow: token=" + token + " pkg=" + pkg
+ " transferFrom=" + transferFrom);
- AppWindowToken wtoken = findAppWindowToken(token);
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
if (wtoken == null) {
Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + token);
return false;
@@ -3419,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
@@ -3432,7 +2973,7 @@
}
}
- if (wtoken != null && wtoken.transferStartingWindow(transferFrom)) {
+ if (wtoken.transferStartingWindow(transferFrom)) {
return true;
}
@@ -3457,14 +2998,14 @@
public void removeAppStartingWindow(IBinder token) {
synchronized (mWindowMap) {
- final AppWindowToken wtoken = mTokenMap.get(token).asAppWindowToken();
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
scheduleRemoveStartingWindowLocked(wtoken);
}
}
public void setAppFullscreen(IBinder token, boolean toOpaque) {
synchronized (mWindowMap) {
- final AppWindowToken atoken = findAppWindowToken(token);
+ final AppWindowToken atoken = mRoot.getAppWindowToken(token);
if (atoken != null) {
atoken.setFillsParent(toOpaque);
setWindowOpaqueLocked(token, toOpaque);
@@ -3479,10 +3020,10 @@
}
}
- public void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
- AppWindowToken wtoken = findAppWindowToken(token);
+ private void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
if (wtoken != null) {
- WindowState win = wtoken.findMainWindow();
+ final WindowState win = wtoken.findMainWindow();
if (win != null) {
win.mWinAnimator.setOpaqueLocked(isOpaque);
}
@@ -3500,14 +3041,12 @@
@Override
public void notifyAppResumed(IBinder token, boolean wasStopped, boolean allowSavedSurface) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "notifyAppResumed()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "notifyAppResumed()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
- final AppWindowToken wtoken;
- wtoken = findAppWindowToken(token);
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
if (wtoken == null) {
Slog.w(TAG_WM, "Attempted to notify resumed of non-existing app token: " + token);
return;
@@ -3518,14 +3057,13 @@
@Override
public void notifyAppStopped(IBinder token) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "notifyAppStopped()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "notifyAppStopped()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
final AppWindowToken wtoken;
- wtoken = findAppWindowToken(token);
+ wtoken = mRoot.getAppWindowToken(token);
if (wtoken == null) {
Slog.w(TAG_WM, "Attempted to notify stopped of non-existing app token: " + token);
return;
@@ -3536,15 +3074,14 @@
@Override
public void setAppVisibility(IBinder token, boolean visible) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppVisibility()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppVisibility()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
AppWindowToken wtoken;
synchronized(mWindowMap) {
- wtoken = findAppWindowToken(token);
+ wtoken = mRoot.getAppWindowToken(token);
if (wtoken == null) {
Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
return;
@@ -3654,8 +3191,7 @@
@Override
public void startAppFreezingScreen(IBinder token, int configChanges) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppFreezingScreen()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppFreezingScreen()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -3665,7 +3201,7 @@
return;
}
- final AppWindowToken wtoken = findAppWindowToken(token);
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
if (wtoken == null || wtoken.appToken == null) {
Slog.w(TAG_WM, "Attempted to freeze screen with non-existing app token: " + wtoken);
return;
@@ -3678,19 +3214,18 @@
@Override
public void stopAppFreezingScreen(IBinder token, boolean force) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppFreezingScreen()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppFreezingScreen()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
- AppWindowToken wtoken = findAppWindowToken(token);
+ final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
if (wtoken == null || wtoken.appToken == null) {
return;
}
final long origId = Binder.clearCallingIdentity();
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + token
- + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.mAppAnimator.freezingScreen);
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + token + ": hidden="
+ + wtoken.hidden + " freezing=" + wtoken.mAppAnimator.freezingScreen);
wtoken.stopFreezingScreen(true, force);
Binder.restoreCallingIdentity(origId);
}
@@ -3698,76 +3233,18 @@
@Override
public void removeAppToken(IBinder token) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "removeAppToken()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
- AppWindowToken wtoken = null;
- AppWindowToken startingToken = null;
- boolean delayed = false;
-
final long origId = Binder.clearCallingIdentity();
- synchronized(mWindowMap) {
- WindowToken basewtoken = mTokenMap.remove(token);
- if (basewtoken != null && (wtoken = basewtoken.asAppWindowToken()) != null) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app token: " + wtoken);
- delayed = wtoken.setVisibility(null, false,
- TRANSIT_UNSET, true, wtoken.voiceInteraction);
- mOpeningApps.remove(wtoken);
- wtoken.waitingToShow = false;
- if (mClosingApps.contains(wtoken)) {
- delayed = true;
- } else if (mAppTransition.isTransitionSet()) {
- mClosingApps.add(wtoken);
- delayed = true;
- }
- if (DEBUG_APP_TRANSITIONS) Slog.v(
- TAG_WM, "Removing app " + wtoken + " delayed=" + delayed
- + " animation=" + wtoken.mAppAnimator.animation
- + " animating=" + wtoken.mAppAnimator.animating);
- if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
- + wtoken + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
- final TaskStack stack = wtoken.mTask.mStack;
- if (delayed && !wtoken.isEmpty()) {
- // set the token aside because it has an active animation to be finished
- if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM,
- "removeAppToken make exiting: " + wtoken);
- stack.mExitingAppTokens.add(wtoken);
- wtoken.mIsExiting = true;
- } else {
- // Make sure there is no animation running on this token,
- // so any windows associated with it will be removed as
- // soon as their animations are complete
- wtoken.mAppAnimator.clearAnimation();
- wtoken.mAppAnimator.animating = false;
- wtoken.removeIfPossible();
- }
-
- wtoken.removed = true;
- if (wtoken.startingData != null) {
- startingToken = wtoken;
- }
- wtoken.stopFreezingScreen(true, true);
- if (mFocusedApp == wtoken) {
- if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + wtoken);
- mFocusedApp = null;
- updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
- mInputMonitor.setFocusedAppLw(null);
- }
- } else {
- Slog.w(TAG_WM, "Attempted to remove non-existing app token: " + token);
+ try {
+ synchronized(mWindowMap) {
+ mRoot.removeAppToken(token);
}
-
- if (!delayed && wtoken != null) {
- wtoken.updateReportedVisibilityLocked();
- }
-
- // Will only remove if startingToken non null.
- scheduleRemoveStartingWindowLocked(startingToken);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- Binder.restoreCallingIdentity(origId);
-
}
void scheduleRemoveStartingWindowLocked(AppWindowToken wtoken) {
@@ -3804,7 +3281,7 @@
displayContent.rebuildAppWindowList();
- // Set displayContent.layoutNeeded if window order changed.
+ // Set displayContent.mLayoutNeeded if window order changed.
final int tmpSize = mTmpWindows.size();
final int winSize = windows.size();
int tmpNdx = 0, winNdx = 0;
@@ -3822,19 +3299,19 @@
if (tmp != win) {
// Window order changed.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
break;
}
}
if (tmpNdx != winNdx) {
// One list was different from the other.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
mTmpWindows.clear();
if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
false /*updateInputWindows*/)) {
- mLayersController.assignLayersLocked(displayContent.getWindowList());
+ displayContent.assignWindowLayers(false /* setLayoutNeeded */);
}
mInputMonitor.setUpdateInputWindowsNeededLw();
@@ -3930,21 +3407,16 @@
}
}
- public void detachStack(int stackId) {
+ public void removeStack(int stackId) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack != null) {
stack.removeIfPossible();
+ mStackIdToStack.remove(stackId);
}
}
}
- public void removeStack(int stackId) {
- synchronized (mWindowMap) {
- mStackIdToStack.remove(stackId);
- }
- }
-
public void removeTask(int taskId) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
@@ -3992,7 +3464,7 @@
}
task.moveTaskToStack(stack, toTop);
final DisplayContent displayContent = stack.getDisplayContent();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -4044,7 +3516,7 @@
}
if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
&& stack.isVisible()) {
- stack.getDisplayContent().layoutNeeded = true;
+ stack.getDisplayContent().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
return stack.getRawFullscreen();
@@ -4063,7 +3535,7 @@
}
public void positionTaskInStack(int taskId, int stackId, int position, Rect bounds,
- Configuration config) {
+ Configuration overrideConfig) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskInStack: positioning taskId=" + taskId
+ " in stackId=" + stackId + " at " + position);
@@ -4079,9 +3551,9 @@
"positionTaskInStack: could not find stackId=" + stackId);
return;
}
- task.positionTaskInStack(stack, position, bounds, config);
+ task.positionTaskInStack(stack, position, bounds, overrideConfig);
final DisplayContent displayContent = stack.getDisplayContent();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -4101,7 +3573,7 @@
}
if (task.resizeLocked(bounds, overrideConfig, forced) && relayout) {
- task.getDisplayContent().layoutNeeded = true;
+ task.getDisplayContent().setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -4665,64 +4137,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
@@ -4738,7 +4153,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;
}
@@ -5014,8 +4431,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);
}
@@ -5033,15 +4451,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) {
}
});
@@ -5308,7 +4724,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,
@@ -5442,44 +4858,50 @@
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();
}
}
}
}
- public void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
- if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked("
- + "alwaysSendConfiguration=" + alwaysSendConfiguration + ")");
+ private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
+ if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
+ + " alwaysSendConfiguration=" + alwaysSendConfiguration
+ + " forceRelayout=" + forceRelayout);
long origId = Binder.clearCallingIdentity();
- boolean changed;
- synchronized(mWindowMap) {
- changed = updateRotationUncheckedLocked(false);
- if (!changed || forceRelayout) {
- getDefaultDisplayContentLocked().layoutNeeded = true;
- mWindowPlacerLocked.performSurfacePlacement();
+
+ try {
+ final boolean rotationChanged;
+ // TODO(multi-display): Update rotation for different displays separately.
+ int displayId = DEFAULT_DISPLAY;
+ synchronized (mWindowMap) {
+ rotationChanged = updateRotationUncheckedLocked(false, displayId);
+ if (!rotationChanged || forceRelayout) {
+ getDefaultDisplayContentLocked().setLayoutNeeded();
+ mWindowPlacerLocked.performSurfacePlacement();
+ }
}
- }
- if (changed || alwaysSendConfiguration) {
- sendNewConfiguration();
+ if (rotationChanged || alwaysSendConfiguration) {
+ sendNewConfiguration(displayId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
-
- 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.
*/
- public 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.
@@ -5488,7 +4910,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
@@ -5496,6 +4918,13 @@
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
return false;
}
+ if (mDisplayFrozen) {
+ // Even if the screen rotation animation has finished (e.g. isAnimating
+ // returns false), there is still some time where we haven't yet unfrozen
+ // the display. We also need to abort rotation here.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, still finishing previous rotation");
+ return false;
+ }
if (!mDisplayEnabled) {
// No point choosing a rotation if the display is not enabled.
@@ -5503,12 +4932,44 @@
return false;
}
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ final WindowList windows = displayContent.getWindowList();
+
+ final int oldRotation = mRotation;
+ int rotation = mPolicy.rotationForOrientationLw(mLastOrientation, mRotation);
+ boolean rotateSeamlessly = mPolicy.shouldRotateSeamlessly(oldRotation, rotation);
+
+ if (rotateSeamlessly) {
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ WindowState w = windows.get(i);
+ // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
+ // to complete (that is, waiting for windows to redraw). It's tempting to check
+ // w.mSeamlessRotationCount but that could be incorrect in the case of window-removal.
+ if (w.mSeamlesslyRotated) {
+ return false;
+ }
+ // In what can only be called an unfortunate workaround we require
+ // seamlessly rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE
+ // flag. Due to limitations in the client API, there is no way for
+ // the client to set this flag in a race free fashion. If we seamlessly rotate
+ // a window which does not have this flag, but then gains it, we will get
+ // an incorrect visual result (rotated viewfinder). This means if we want to
+ // support seamlessly rotating windows which could gain this flag, we can't
+ // rotate windows without it. This limits seamless rotation in N to camera framework
+ // users, windows without children, and native code. This is unfortunate but
+ // having the camera work is our primary goal.
+ if (w.isChildWindow() & w.isVisibleNow() &&
+ !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse()) {
+ rotateSeamlessly = false;
+ }
+ }
+ }
+
// TODO: Implement forced rotation changes.
// Set mAltOrientation to indicate that the application is receiving
// an orientation that has different metrics than it expected.
// eg. Portrait instead of Landscape.
- int rotation = mPolicy.rotationForOrientationLw(mLastOrientation, mRotation);
boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(
mLastOrientation, rotation);
@@ -5531,8 +4992,6 @@
+ ", lastOrientation=" + mLastOrientation);
}
- int oldRotation = mRotation;
-
mRotation = rotation;
mAltOrientation = altOrientation;
mPolicy.setRotationLw(mRotation);
@@ -5541,53 +5000,29 @@
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
final int[] anim = new int[2];
if (displayContent.isDimming()) {
anim[0] = anim[1] = 0;
} else {
mPolicy.selectRotationAnimationLw(anim);
}
- boolean rotateSeamlessly = mPolicy.shouldRotateSeamlessly(oldRotation, mRotation);
- final WindowList windows = displayContent.getWindowList();
- // We can't rotate seamlessly while an existing seamless rotation is still
- // waiting on windows to finish drawing.
- if (rotateSeamlessly) {
- for (int i = windows.size() - 1; i >= 0; i--) {
- WindowState w = windows.get(i);
- if (w.mSeamlesslyRotated) {
- rotateSeamlessly = false;
- break;
- }
- // In what can only be called an unfortunate workaround we require
- // seamlessly rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE
- // flag. Due to limitations in the client API, there is no way for
- // the client to set this flag in a race free fashion. If we seamlessly rotate
- // a window which does not have this flag, but then gains it, we will get
- // an incorrect visual result (rotated viewfinder). This means if we want to
- // support seamlessly rotating windows which could gain this flag, we can't
- // rotate windows without it. This limits seamless rotation in N to camera framework
- // users, windows without children, and native code. This is unfortunate but
- // having the camera work is our primary goal.
- if (w.isChildWindow() & w.isVisibleNow() &&
- !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse()) {
- rotateSeamlessly = false;
- }
- }
- }
if (!rotateSeamlessly) {
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.
// When we are rotating seamlessly, we allow the elements to transition
// to their rotated state independently and without a freeze required.
screenRotationAnimation = null;
+
+ // We have to reset this in case a window was removed before it
+ // finished seamless rotation.
+ mSeamlessRotationCount = 0;
}
// We need to update our screen size information to match the new rotation. If the rotation
@@ -5595,7 +5030,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(mGlobalConfiguration.uiMode);
+ updateDisplayAndOrientationLocked(displayContent.getConfiguration().uiMode, displayId);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
@@ -5663,7 +5098,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);
}
@@ -6128,28 +5563,44 @@
}
/**
- * 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 {
- 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
+ // placement to unfreeze the display since we froze it when the rotation was updated
+ // in updateRotationUncheckedLocked.
+ synchronized (mWindowMap) {
+ if (mWaitingForConfig) {
+ mWaitingForConfig = false;
+ mLastFinishedFreezeSource = "config-unchanged";
+ mRoot.getDisplayContent(displayId).setLayoutNeeded();
+ mWindowPlacerLocked.performSurfacePlacement();
+ }
+ }
+ }
} catch (RemoteException e) {
}
}
- 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;
}
@@ -6258,9 +5709,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
@@ -6321,9 +5771,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;
@@ -6601,38 +6050,39 @@
@Override
public void pauseKeyDispatching(IBinder _token) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "pauseKeyDispatching()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "pauseKeyDispatching()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized (mWindowMap) {
- WindowToken token = mTokenMap.get(_token);
- if (token != null) {
- mInputMonitor.pauseDispatchingLw(token);
+ final ArrayList<WindowToken> tokens = mRoot.getWindowTokens(_token);
+ if (tokens != null && !tokens.isEmpty()) {
+ for (int i = tokens.size() - 1; i >= 0; --i) {
+ mInputMonitor.pauseDispatchingLw(tokens.get(i));
+ }
}
}
}
@Override
public void resumeKeyDispatching(IBinder _token) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "resumeKeyDispatching()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "resumeKeyDispatching()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized (mWindowMap) {
- WindowToken token = mTokenMap.get(_token);
- if (token != null) {
- mInputMonitor.resumeDispatchingLw(token);
+ final ArrayList<WindowToken> tokens = mRoot.getWindowTokens(_token);
+ if (tokens != null && !tokens.isEmpty()) {
+ for (int i = tokens.size() - 1; i >= 0; --i) {
+ mInputMonitor.resumeDispatchingLw(tokens.get(i));
+ }
}
}
}
@Override
public void setEventDispatching(boolean enabled) {
- if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setEventDispatching()")) {
+ if (!checkCallingPermission(MANAGE_APP_TOKENS, "setEventDispatching()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
@@ -6884,7 +6334,7 @@
synchronized(mWindowMap) {
// TODO(multidisplay): Accessibility supported only of default desiplay.
if (mAccessibilityController != null && getDefaultDisplayContentLocked()
- .getDisplayId() == Display.DEFAULT_DISPLAY) {
+ .getDisplayId() == DEFAULT_DISPLAY) {
accessibilityController = mAccessibilityController;
}
@@ -6961,11 +6411,9 @@
View view = null;
try {
- final Configuration overrideConfig = wtoken != null && wtoken.mTask != null
- ? wtoken.mTask.mOverrideConfig : 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);
}
@@ -7103,21 +6551,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;
}
@@ -7226,8 +6660,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;
}
@@ -7418,7 +6853,7 @@
break;
case WALLPAPER_DRAW_PENDING_TIMEOUT: {
synchronized (mWindowMap) {
- if (mWallpaperControllerLocked.processWallpaperDrawPendingTimeout()) {
+ if (mRoot.mWallpaperController.processWallpaperDrawPendingTimeout()) {
mWindowPlacerLocked.performSurfacePlacement();
}
}
@@ -7516,8 +6951,8 @@
if (w.mSeamlesslyRotated) {
layoutNeeded = true;
w.setDisplayLayoutNeeded();
+ markForSeamlessRotation(w, false);
}
- w.mSeamlesslyRotated = false;
}
if (layoutNeeded) {
mWindowPlacerLocked.performSurfacePlacement();
@@ -7564,44 +6999,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?
@@ -7650,7 +7050,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();
@@ -7685,7 +7085,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();
@@ -7768,7 +7168,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();
@@ -7817,7 +7217,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");
}
@@ -7848,7 +7248,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");
}
@@ -7908,17 +7308,21 @@
return;
}
configureDisplayPolicyLocked(displayContent);
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
- boolean configChanged = updateOrientationFromAppTokensLocked(false);
- mTempConfiguration.setTo(mGlobalConfiguration);
- computeScreenConfigurationLocked(mTempConfiguration);
- configChanged |= mGlobalConfiguration.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();
@@ -8040,14 +7444,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.
@@ -8056,108 +7463,6 @@
return changes;
}
- void updateResizingWindows(final WindowState w) {
- final WindowStateAnimator winAnimator = w.mWinAnimator;
- if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq && !w.isGoneForLayoutLw()) {
- final Task task = w.getTask();
- // In the case of stack bound animations, the window frames will update (unlike other
- // animations which just modify various transformation properties). We don't want to
- // notify the client of frame changes in this case. Not only is it a lot of churn, but
- // the frame may not correspond to the surface size or the onscreen area at various
- // phases in the animation, and the client will become sad and confused.
- if (task != null && task.mStack.getBoundsAnimating()) {
- return;
- }
- w.setReportResizeHints();
- boolean configChanged = w.isConfigChanged();
- if (DEBUG_CONFIGURATION && configChanged) {
- Slog.v(TAG_WM, "Win " + w + " config changed: " + mGlobalConfiguration);
- }
- final boolean dragResizingChanged = w.isDragResizeChanged()
- && !w.isDragResizingChangeReported();
-
- if (localLOGV) Slog.v(TAG_WM, "Resizing " + w + ": configChanged=" + configChanged
- + " dragResizingChanged=" + dragResizingChanged + " last=" + w.mLastFrame
- + " frame=" + w.mFrame);
-
- // We update mLastFrame always rather than in the conditional with the
- // last inset variables, because mFrameSizeChanged only tracks the
- // width and height changing.
- w.mLastFrame.set(w.mFrame);
-
- if (w.mContentInsetsChanged
- || w.mVisibleInsetsChanged
- || winAnimator.mSurfaceResized
- || w.mOutsetsChanged
- || w.mFrameSizeChanged
- || configChanged
- || dragResizingChanged
- || !w.isResizedWhileNotDragResizingReported()) {
- if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
- Slog.v(TAG_WM, "Resize reasons for w=" + w + ": "
- + " contentInsetsChanged=" + w.mContentInsetsChanged
- + " " + w.mContentInsets.toShortString()
- + " visibleInsetsChanged=" + w.mVisibleInsetsChanged
- + " " + w.mVisibleInsets.toShortString()
- + " stableInsetsChanged=" + w.mStableInsetsChanged
- + " " + w.mStableInsets.toShortString()
- + " outsetsChanged=" + w.mOutsetsChanged
- + " " + w.mOutsets.toShortString()
- + " surfaceResized=" + winAnimator.mSurfaceResized
- + " configChanged=" + configChanged
- + " dragResizingChanged=" + dragResizingChanged
- + " resizedWhileNotDragResizingReported="
- + w.isResizedWhileNotDragResizingReported());
- }
-
- // If it's a dead window left on screen, and the configuration changed,
- // there is nothing we can do about it. Remove the window now.
- if (w.mAppToken != null && w.mAppDied) {
- w.mAppToken.removeDeadWindows();
- return;
- }
-
- w.mLastOverscanInsets.set(w.mOverscanInsets);
- w.mLastContentInsets.set(w.mContentInsets);
- w.mLastVisibleInsets.set(w.mVisibleInsets);
- w.mLastStableInsets.set(w.mStableInsets);
- w.mLastOutsets.set(w.mOutsets);
- makeWindowFreezingScreenIfNeededLocked(w);
- // If the orientation is changing, or we're starting or ending
- // a drag resizing action, then we need to hold off on unfreezing
- // the display until this window has been redrawn; to do that,
- // we need to go through the process of getting informed by the
- // application when it has finished drawing.
- if (w.mOrientationChanging || dragResizingChanged
- || w.isResizedWhileNotDragResizing()) {
- if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
- Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
- + ", mDrawState=DRAW_PENDING in " + w
- + ", surfaceController " + winAnimator.mSurfaceController);
- }
- winAnimator.mDrawState = DRAW_PENDING;
- if (w.mAppToken != null) {
- w.mAppToken.clearAllDrawn();
- }
- }
- if (!mResizingWindows.contains(w)) {
- if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Resizing window " + w);
- mResizingWindows.add(w);
- }
- } else if (w.mOrientationChanging) {
- if (w.isDrawnLw()) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Orientation not waiting for draw in "
- + w + ", surfaceController " + winAnimator.mSurfaceController);
- w.mOrientationChanging = false;
- w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mDisplayFreezeTime);
- }
- }
- }
- }
-
void checkDrawnWindowsLocked() {
if (mWaitingForDrawn.isEmpty() || mWaitingForDrawnCallback == null) {
return;
@@ -8207,10 +7512,10 @@
} else {
if (DEBUG_KEEP_SCREEN_ON) {
Slog.d(TAG_KEEP_SCREEN_ON, "Releasing screen wakelock, obscured by "
- + mRoot.mObsuringWindow);
+ + mRoot.mObscuringWindow);
}
mLastWakeLockHoldingWindow = null;
- mLastWakeLockObscuringWindow = mRoot.mObsuringWindow;
+ mLastWakeLockObscuringWindow = mRoot.mObscuringWindow;
mPolicy.keepScreenOnStoppedLw();
mHoldingScreenWakeLock.release();
}
@@ -8231,20 +7536,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) {
@@ -8255,11 +7546,11 @@
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) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
newFocus = mRoot.computeFocusedWindow();
}
@@ -8280,13 +7571,13 @@
} 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 */);
}
}
if ((focusChanged & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
// The change in focus caused us to need to do a layout. Okay.
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
mWindowPlacerLocked.performLayoutLockedInner(displayContent, true /*initial*/,
updateInputWindows);
@@ -8362,16 +7653,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();
@@ -8458,7 +7740,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
@@ -8472,11 +7754,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();
}
}
@@ -8577,8 +7859,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;
}
@@ -8591,26 +7873,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;
}
@@ -8624,68 +7887,25 @@
}
}
- private static final class HideNavInputConsumer extends InputConsumerImpl
- implements WindowManagerPolicy.InputConsumer {
- private final InputEventReceiver mInputEventReceiver;
-
- HideNavInputConsumer(WindowManagerService service, Looper looper,
- InputEventReceiver.Factory inputEventReceiverFactory) {
- super(service, "input consumer", null);
- mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
- mClientChannel, looper);
- }
-
- @Override
- public void dismiss() {
- if (mService.removeInputConsumer()) {
- synchronized (mService.mWindowMap) {
- mInputEventReceiver.dispose();
- disposeChannelsLw();
- }
- }
- }
- }
-
@Override
- public WindowManagerPolicy.InputConsumer addInputConsumer(Looper looper,
+ public WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
InputEventReceiver.Factory inputEventReceiverFactory) {
synchronized (mWindowMap) {
- HideNavInputConsumer inputConsumerImpl = new HideNavInputConsumer(
- this, looper, inputEventReceiverFactory);
- mInputConsumer = inputConsumerImpl;
- mInputMonitor.updateInputWindowsLw(true);
- return inputConsumerImpl;
- }
- }
-
- boolean removeInputConsumer() {
- synchronized (mWindowMap) {
- if (mInputConsumer != null) {
- mInputConsumer = null;
- mInputMonitor.updateInputWindowsLw(true);
- return true;
- }
- return false;
+ return mInputMonitor.createInputConsumer(looper, name, inputEventReceiverFactory);
}
}
@Override
- public void createWallpaperInputConsumer(InputChannel inputChannel) {
+ public void createInputConsumer(String name, InputChannel inputChannel) {
synchronized (mWindowMap) {
- mWallpaperInputConsumer = new InputConsumerImpl(this, "wallpaper input", inputChannel);
- mWallpaperInputConsumer.mWindowHandle.hasWallpaper = true;
- mInputMonitor.updateInputWindowsLw(true);
+ mInputMonitor.createInputConsumer(name, inputChannel);
}
}
@Override
- public void removeWallpaperInputConsumer() {
+ public boolean destroyInputConsumer(String name) {
synchronized (mWindowMap) {
- if (mWallpaperInputConsumer != null) {
- mWallpaperInputConsumer.disposeChannelsLw();
- mWallpaperInputConsumer = null;
- mInputMonitor.updateInputWindowsLw(true);
- }
+ return mInputMonitor.destroyInputConsumer(name);
}
}
@@ -8755,7 +7975,7 @@
public void notifyAppRelaunching(IBinder token) {
synchronized (mWindowMap) {
- AppWindowToken appWindow = findAppWindowToken(token);
+ final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
if (appWindow != null) {
appWindow.startRelaunching();
}
@@ -8764,7 +7984,7 @@
public void notifyAppRelaunchingFinished(IBinder token) {
synchronized (mWindowMap) {
- AppWindowToken appWindow = findAppWindowToken(token);
+ final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
if (appWindow != null) {
appWindow.finishRelaunching();
}
@@ -8773,7 +7993,7 @@
public void notifyAppRelaunchesCleared(IBinder token) {
synchronized (mWindowMap) {
- final AppWindowToken appWindow = findAppWindowToken(token);
+ final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
if (appWindow != null) {
appWindow.clearRelaunching();
}
@@ -8785,33 +8005,20 @@
return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
}
- void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
+ private void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
mPolicy.dump(" ", pw, args);
}
- void dumpAnimatorLocked(PrintWriter pw, String[] args, boolean dumpAll) {
+ private void dumpAnimatorLocked(PrintWriter pw, String[] args, boolean dumpAll) {
pw.println("WINDOW MANAGER ANIMATOR STATE (dumpsys window animator)");
mAnimator.dumpLocked(pw, " ", dumpAll);
}
- void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
+ private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
- if (!mTokenMap.isEmpty()) {
- pw.println(" All tokens:");
- Iterator<WindowToken> it = mTokenMap.values().iterator();
- while (it.hasNext()) {
- WindowToken token = it.next();
- pw.print(" "); pw.print(token);
- if (dumpAll) {
- pw.println(':');
- token.dump(pw, " ");
- } else {
- pw.println();
- }
- }
- }
- mWallpaperControllerLocked.dumpTokens(pw, " ", dumpAll);
+ mRoot.dumpTokens(pw, dumpAll);
+ mRoot.mWallpaperController.dumpTokens(pw, " ", dumpAll);
if (!mFinishedStarting.isEmpty()) {
pw.println();
pw.println(" Finishing start of application tokens:");
@@ -8838,7 +8045,7 @@
}
}
- void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
+ private void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
for (int i=0; i<mSessions.size(); i++) {
Session s = mSessions.valueAt(i);
@@ -8847,13 +8054,13 @@
}
}
- void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
+ private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
dumpWindowsNoHeaderLocked(pw, dumpAll, windows);
}
- void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll,
+ private void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
@@ -8959,7 +8166,7 @@
}
}
pw.println();
- pw.print(" mGlobalConfiguration="); pw.println(mGlobalConfiguration);
+ pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
if (mLastFocus != mCurrentFocus) {
@@ -8995,8 +8202,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);
@@ -9260,7 +8466,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");
}
@@ -9268,28 +8474,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));
}
@@ -9342,7 +8534,7 @@
*/
public void setWillReplaceWindow(IBinder token, boolean animate) {
synchronized (mWindowMap) {
- final AppWindowToken appWindowToken = findAppWindowToken(token);
+ final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ token);
@@ -9364,9 +8556,9 @@
*/
// TODO: The s at the end of the method name is the only difference with the name of the method
// above. We should combine them or find better names.
- public void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
+ void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
synchronized (mWindowMap) {
- final AppWindowToken appWindowToken = findAppWindowToken(token);
+ final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ token);
@@ -9394,7 +8586,7 @@
*/
public void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
synchronized (mWindowMap) {
- final AppWindowToken appWindowToken = findAppWindowToken(token);
+ final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
if (appWindowToken == null) {
Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token "
+ token);
@@ -9512,15 +8704,19 @@
}
@Override
- public void getStableInsets(Rect outInsets) throws RemoteException {
+ public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
synchronized (mWindowMap) {
- getStableInsetsLocked(outInsets);
+ getStableInsetsLocked(displayId, outInsets);
}
}
- void getStableInsetsLocked(Rect outInsets) {
- final DisplayInfo di = getDefaultDisplayInfoLocked();
- mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, outInsets);
+ void getStableInsetsLocked(int displayId, Rect outInsets) {
+ outInsets.setEmpty();
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc != null) {
+ final DisplayInfo di = dc.getDisplayInfo();
+ mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, outInsets);
+ }
}
private void getNonDecorInsetsLocked(Rect outInsets) {
@@ -9536,7 +8732,7 @@
*/
public void subtractStableInsets(Rect inOutBounds) {
synchronized (mWindowMap) {
- getStableInsetsLocked(mTmpRect2);
+ getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect2);
final DisplayInfo di = getDefaultDisplayInfoLocked();
mTmpRect.set(0, 0, di.logicalWidth, di.logicalHeight);
subtractInsets(mTmpRect, mTmpRect2, inOutBounds);
@@ -9574,6 +8770,7 @@
*/
public int getSmallestWidthForTaskBounds(Rect bounds) {
synchronized (mWindowMap) {
+ // TODO(multi-display): Use correct display content here
return getDefaultDisplayContentLocked().getDockedDividerController()
.getSmallestWidthDpForBounds(bounds);
}
@@ -9676,6 +8873,27 @@
mPolicy.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
}
+ void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
+ if (seamlesslyRotated == w.mSeamlesslyRotated) {
+ return;
+ }
+ w.mSeamlesslyRotated = seamlesslyRotated;
+ if (seamlesslyRotated) {
+ mSeamlessRotationCount++;
+ } else {
+ mSeamlessRotationCount--;
+ }
+ if (mSeamlessRotationCount == 0) {
+ if (DEBUG_ORIENTATION) {
+ Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
+ }
+ final int displayId = w.getDisplayId();
+ if (updateRotationUncheckedLocked(false, displayId)) {
+ mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+ }
+ }
+ }
+
private final class LocalService extends WindowManagerInternal {
@Override
public void requestTraversalFromDisplayManager() {
@@ -9798,24 +9016,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()) {
@@ -9839,9 +9040,12 @@
public void removeWindowToken(IBinder token, boolean removeWindows) {
synchronized(mWindowMap) {
if (removeWindows) {
- final WindowToken wtoken = mTokenMap.remove(token);
- if (wtoken != null) {
- wtoken.removeAllWindows();
+ final ArrayList<WindowToken> removedTokens = mRoot.removeWindowToken(token);
+ if (removedTokens != null && !removedTokens.isEmpty()) {
+ for (int i = removedTokens.size() - 1; i >= 0; --i) {
+ final WindowToken wtoken = removedTokens.get(i);
+ wtoken.removeAllWindows();
+ }
}
}
WindowManagerService.this.removeWindowToken(token);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c3affeb..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;
@@ -71,6 +70,7 @@
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -85,8 +85,8 @@
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;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -131,6 +131,7 @@
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowManagerService.localLOGV;
import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
+import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
@@ -155,7 +156,7 @@
// to capture touch events in that area.
static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
- static final boolean DEBUG_DISABLE_SAVING_SURFACES = false;
+ private static final boolean DEBUG_DISABLE_SAVING_SURFACES = false;
final WindowManagerService mService;
final WindowManagerPolicy mPolicy;
@@ -180,7 +181,7 @@
final boolean mLayoutAttached;
final boolean mIsImWindow;
final boolean mIsWallpaper;
- final boolean mIsFloatingLayer;
+ private final boolean mIsFloatingLayer;
int mSeq;
boolean mEnforceSizeCompat;
int mViewVisibility;
@@ -197,16 +198,16 @@
* animation is done.
*/
boolean mPolicyVisibilityAfterAnim = true;
- boolean mAppOpVisibility = true;
+ private boolean mAppOpVisibility = true;
boolean mPermanentlyHidden; // the window should never be shown again
boolean mAppFreezing;
boolean mHidden; // Used to determine if to show child windows.
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
- boolean mDragResizing;
- boolean mDragResizingChangeReported;
- int mResizeMode;
+ private boolean mDragResizing;
+ private boolean mDragResizingChangeReported;
+ private int mResizeMode;
- RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
+ private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
/**
* The window size that was requested by the application. These are in
@@ -214,8 +215,8 @@
*/
int mRequestedWidth;
int mRequestedHeight;
- int mLastRequestedWidth;
- int mLastRequestedHeight;
+ private int mLastRequestedWidth;
+ private int mLastRequestedHeight;
int mLayer;
boolean mHaveFrame;
@@ -224,14 +225,12 @@
int mLayoutSeq = -1;
- private final Configuration mTmpConfig = new Configuration();
- // Represents the changes from our override configuration applied
- // to the global configuration. This is the only form of configuration
- // which is suitable for delivery to the client.
- private Configuration mMergedConfiguration = new Configuration();
- // Sticky answer to isConfigChanged(), remains true until new Configuration is assigned.
- // Used only on {@link #TYPE_KEYGUARD}.
- private boolean mConfigHasChanged;
+ /**
+ * Used to store last reported to client configuration and check if we have newer available.
+ * We'll send configuration to client only if it is different from the last applied one and
+ * client won't perform unnecessary updates.
+ */
+ private final Configuration mLastReportedConfiguration = new Configuration();
/**
* Actual position of the surface shown on-screen (may be modified by animation). These are
@@ -244,8 +243,8 @@
* coordinate space (without compatibility scale applied).
*/
final Rect mVisibleInsets = new Rect();
- final Rect mLastVisibleInsets = new Rect();
- boolean mVisibleInsetsChanged;
+ private final Rect mLastVisibleInsets = new Rect();
+ private boolean mVisibleInsetsChanged;
/**
* Insets that are covered by system windows (such as the status bar) and
@@ -254,31 +253,31 @@
*/
final Rect mContentInsets = new Rect();
final Rect mLastContentInsets = new Rect();
- boolean mContentInsetsChanged;
+ private boolean mContentInsetsChanged;
/**
* Insets that determine the area covered by the display overscan region. These are in the
* application's coordinate space (without compatibility scale applied).
*/
final Rect mOverscanInsets = new Rect();
- final Rect mLastOverscanInsets = new Rect();
- boolean mOverscanInsetsChanged;
+ private final Rect mLastOverscanInsets = new Rect();
+ private boolean mOverscanInsetsChanged;
/**
* Insets that determine the area covered by the stable system windows. These are in the
* application's coordinate space (without compatibility scale applied).
*/
final Rect mStableInsets = new Rect();
- final Rect mLastStableInsets = new Rect();
- boolean mStableInsetsChanged;
+ private final Rect mLastStableInsets = new Rect();
+ private boolean mStableInsetsChanged;
/**
* Outsets determine the area outside of the surface where we want to pretend that it's possible
* to draw anyway.
*/
final Rect mOutsets = new Rect();
- final Rect mLastOutsets = new Rect();
- boolean mOutsetsChanged = false;
+ private final Rect mLastOutsets = new Rect();
+ private boolean mOutsetsChanged = false;
/**
* Set to true if we are waiting for this window to receive its
@@ -321,14 +320,14 @@
// "Real" frame that the application sees, in display coordinate space.
final Rect mFrame = new Rect();
final Rect mLastFrame = new Rect();
- boolean mFrameSizeChanged = false;
+ private boolean mFrameSizeChanged = false;
// Frame that is scaled to the application's coordinate space when in
// screen size compatibility mode.
final Rect mCompatFrame = new Rect();
final Rect mContainingFrame = new Rect();
- final Rect mParentFrame = new Rect();
+ private final Rect mParentFrame = new Rect();
// The entire screen area of the {@link TaskStack} this window is in. Usually equal to the
// screen area of the device.
@@ -338,11 +337,11 @@
// is mostly a special case for TV where some displays don’t have the entire display usable.
// {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} flag can be used to allow
// window display contents to extend into the overscan region.
- final Rect mOverscanFrame = new Rect();
+ private final Rect mOverscanFrame = new Rect();
// The display frame minus the stable insets. This value is always constant regardless of if
// the status bar or navigation bar is visible.
- final Rect mStableFrame = new Rect();
+ private final Rect mStableFrame = new Rect();
// The area not occupied by the status and navigation bars. So, if both status and navigation
// bars are visible, the decor frame is equal to the stable frame.
@@ -350,7 +349,7 @@
// Equal to the decor frame if the IME (e.g. keyboard) is not present. Equal to the decor frame
// minus the area occupied by the IME if the IME is present.
- final Rect mContentFrame = new Rect();
+ private final Rect mContentFrame = new Rect();
// Legacy stuff. Generally equal to the content frame expect when the IME for older apps
// displays hint text.
@@ -358,13 +357,13 @@
// Frame that includes dead area outside of the surface but where we want to pretend that it's
// possible to draw.
- final Rect mOutsetFrame = new Rect();
+ private final Rect mOutsetFrame = new Rect();
/**
* Usually empty. Set to the task's tempInsetFrame. See
*{@link android.app.IActivityManager#resizeDockedStack}.
*/
- final Rect mInsetFrame = new Rect();
+ private final Rect mInsetFrame = new Rect();
boolean mContentChanged;
@@ -446,7 +445,7 @@
* or some other higher level component said so (e.g. activity manager).
* TODO: We should either have different booleans for the removal reason or use a bit-field.
*/
- boolean mWindowRemovalAllowed;
+ private boolean mWindowRemovalAllowed;
/**
* Temp for keeping track of windows that have been removed when
@@ -457,20 +456,17 @@
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
- InputChannel mClientChannel;
+ private InputChannel mClientChannel;
// Used to improve performance of toString()
- String mStringNameCache;
- CharSequence mLastTitle;
- boolean mWasExiting;
+ private String mStringNameCache;
+ private CharSequence mLastTitle;
+ private boolean mWasExiting;
final WindowStateAnimator mWinAnimator;
boolean mHasSurface = false;
- boolean mNotOnAppsDisplay = false;
- DisplayContent mDisplayContent;
-
/** When true this window can be displayed on screens owther than mOwnerUid's */
private boolean mShowToOwnerOnly;
@@ -486,26 +482,26 @@
// Whether the window was visible when we set the app to invisible last time. WM uses
// this as a hint to restore the surface (if available) for early animation next time
// the app is brought visible.
- boolean mWasVisibleBeforeClientHidden;
+ private boolean mWasVisibleBeforeClientHidden;
// This window will be replaced due to relaunch. This allows window manager
// to differentiate between simple removal of a window and replacement. In the latter case it
// will preserve the old window until the new one is drawn.
boolean mWillReplaceWindow = false;
// If true, the replaced window was already requested to be removed.
- boolean mReplacingRemoveRequested = false;
+ private boolean mReplacingRemoveRequested = false;
// Whether the replacement of the window should trigger app transition animation.
- boolean mAnimateReplacingWindow = false;
+ private boolean mAnimateReplacingWindow = false;
// If not null, the window that will be used to replace the old one. This is being set when
// the window is added and unset when this window reports its first draw.
- WindowState mReplacementWindow = null;
+ private WindowState mReplacementWindow = null;
// For the new window in the replacement transition, if we have
// requested to replace without animation, then we should
// make sure we also don't apply an enter animation for
// the new window.
boolean mSkipEnterAnimationForSeamlessReplacement = false;
// Whether this window is being moved via the resize API
- boolean mMovedByResize;
+ private boolean mMovedByResize;
/**
* Wake lock for drawing.
@@ -514,7 +510,7 @@
* who is preventing the system from suspending.
* This lock is only acquired on first use.
*/
- PowerManager.WakeLock mDrawLock;
+ private PowerManager.WakeLock mDrawLock;
final private Rect mTmpRect = new Rect();
@@ -546,7 +542,7 @@
private static final Region sEmptyRegion = new Region();
/**
- * Compares to window sub-layers and returns -1 if the first is lesser than the second in terms
+ * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
private static final Comparator<WindowState> sWindowSubLayerComparator = (w1, w2) -> {
@@ -563,7 +559,7 @@
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
- int viewVisibility, final DisplayContent displayContent, int ownerId) {
+ int viewVisibility, int ownerId) {
mService = service;
mSession = s;
mClient = c;
@@ -587,7 +583,6 @@
};
mAttrs.copyFrom(a);
mViewVisibility = viewVisibility;
- mDisplayContent = displayContent;
mPolicy = mService.mPolicy;
mContext = mService.mContext;
DeathRecipient deathRecipient = new DeathRecipient();
@@ -643,15 +638,10 @@
}
mIsFloatingLayer = mIsImWindow || mIsWallpaper;
- if (mAppToken != null) {
- final DisplayContent appDisplay = getDisplayContent();
- mNotOnAppsDisplay = displayContent != appDisplay;
-
- if (mAppToken.showForAllUsers) {
- // Windows for apps that can show for all users should also show when the
- // device is locked.
- mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
- }
+ if (mAppToken != null && mAppToken.showForAllUsers) {
+ // Windows for apps that can show for all users should also show when the device is
+ // locked.
+ mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
}
mWinAnimator = new WindowStateAnimator(this);
@@ -665,8 +655,7 @@
mYOffset = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
- mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
- displayContent.getDisplayId());
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, getDisplayId());
}
void attach() {
@@ -714,6 +703,7 @@
final Task task = getTask();
final boolean fullscreenTask = !isInMultiWindowMode();
final boolean windowsAreFloating = task != null && task.isFloating();
+ final DisplayContent dc = getDisplayContent();
// If the task has temp inset bounds set, we have to make sure all its windows uses
// the temp inset frame. Otherwise different display frames get applied to the main
@@ -779,8 +769,7 @@
layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
- mTmpRect.set(0, 0, mDisplayContent.getDisplayInfo().logicalWidth,
- mDisplayContent.getDisplayInfo().logicalHeight);
+ mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
subtractInsets(mDisplayFrame, layoutContainingFrame, df, mTmpRect);
if (!layoutInParentFrame()) {
subtractInsets(mContainingFrame, layoutContainingFrame, pf, mTmpRect);
@@ -852,7 +841,7 @@
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
- mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
+ dc.getDockedDividerController().positionDockedStackedDivider(mFrame);
mContentFrame.set(mFrame);
if (!mFrame.equals(mLastFrame)) {
mMovedByResize = true;
@@ -899,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
@@ -950,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);
}
}
@@ -1018,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
@@ -1081,20 +1049,122 @@
|| mOutsetsChanged || mFrameSizeChanged;
}
- public DisplayContent getDisplayContent() {
- if (mAppToken == null || mNotOnAppsDisplay) {
- return mDisplayContent;
+ /**
+ * Adds the window to the resizing list if any of the parameters we use to track the window
+ * dimensions or insets have changed.
+ */
+ void updateResizingWindowIfNeeded() {
+ final WindowStateAnimator winAnimator = mWinAnimator;
+ if (!mHasSurface || mService.mLayoutSeq != mLayoutSeq || isGoneForLayoutLw()) {
+ return;
}
- final TaskStack stack = getStack();
- return stack == null ? mDisplayContent : stack.getDisplayContent();
+
+ final Task task = getTask();
+ // In the case of stack bound animations, the window frames will update (unlike other
+ // animations which just modify various transformation properties). We don't want to
+ // notify the client of frame changes in this case. Not only is it a lot of churn, but
+ // the frame may not correspond to the surface size or the onscreen area at various
+ // phases in the animation, and the client will become sad and confused.
+ if (task != null && task.mStack.getBoundsAnimating()) {
+ return;
+ }
+
+ setReportResizeHints();
+ boolean configChanged = isConfigChanged();
+ if (DEBUG_CONFIGURATION && configChanged) {
+ Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
+ }
+
+ final boolean dragResizingChanged = isDragResizeChanged()
+ && !isDragResizingChangeReported();
+
+ if (localLOGV) Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+ + " dragResizingChanged=" + dragResizingChanged + " last=" + mLastFrame
+ + " frame=" + mFrame);
+
+ // We update mLastFrame always rather than in the conditional with the last inset
+ // variables, because mFrameSizeChanged only tracks the width and height changing.
+ mLastFrame.set(mFrame);
+
+ if (mContentInsetsChanged
+ || mVisibleInsetsChanged
+ || winAnimator.mSurfaceResized
+ || mOutsetsChanged
+ || mFrameSizeChanged
+ || configChanged
+ || dragResizingChanged
+ || !isResizedWhileNotDragResizingReported()) {
+ if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
+ Slog.v(TAG_WM, "Resize reasons for w=" + this + ": "
+ + " contentInsetsChanged=" + mContentInsetsChanged
+ + " " + mContentInsets.toShortString()
+ + " visibleInsetsChanged=" + mVisibleInsetsChanged
+ + " " + mVisibleInsets.toShortString()
+ + " stableInsetsChanged=" + mStableInsetsChanged
+ + " " + mStableInsets.toShortString()
+ + " outsetsChanged=" + mOutsetsChanged
+ + " " + mOutsets.toShortString()
+ + " surfaceResized=" + winAnimator.mSurfaceResized
+ + " configChanged=" + configChanged
+ + " dragResizingChanged=" + dragResizingChanged
+ + " resizedWhileNotDragResizingReported="
+ + isResizedWhileNotDragResizingReported());
+ }
+
+ // If it's a dead window left on screen, and the configuration changed, there is nothing
+ // we can do about it. Remove the window now.
+ if (mAppToken != null && mAppDied) {
+ mAppToken.removeDeadWindows();
+ return;
+ }
+
+ mLastOverscanInsets.set(mOverscanInsets);
+ mLastContentInsets.set(mContentInsets);
+ mLastVisibleInsets.set(mVisibleInsets);
+ mLastStableInsets.set(mStableInsets);
+ mLastOutsets.set(mOutsets);
+ mService.makeWindowFreezingScreenIfNeededLocked(this);
+
+ // If the orientation is changing, or we're starting or ending a drag resizing action,
+ // then we need to hold off on unfreezing the display until this window has been
+ // redrawn; to do that, we need to go through the process of getting informed by the
+ // application when it has finished drawing.
+ if (mOrientationChanging || dragResizingChanged || isResizedWhileNotDragResizing()) {
+ if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
+ Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
+ + ", mDrawState=DRAW_PENDING in " + this
+ + ", surfaceController " + winAnimator.mSurfaceController);
+ }
+ winAnimator.mDrawState = DRAW_PENDING;
+ if (mAppToken != null) {
+ mAppToken.clearAllDrawn();
+ }
+ }
+ if (!mService.mResizingWindows.contains(this)) {
+ if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG_WM, "Resizing window " + this);
+ mService.mResizingWindows.add(this);
+ }
+ } else if (mOrientationChanging) {
+ if (isDrawnLw()) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Orientation not waiting for draw in "
+ + this + ", surfaceController " + winAnimator.mSurfaceController);
+ mOrientationChanging = false;
+ mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+ - mService.mDisplayFreezeTime);
+ }
+ }
}
- public DisplayInfo getDisplayInfo() {
+ DisplayContent getDisplayContent() {
+ return mToken.getDisplayContent();
+ }
+
+ DisplayInfo getDisplayInfo() {
final DisplayContent displayContent = getDisplayContent();
return displayContent != null ? displayContent.getDisplayInfo() : null;
}
- public int getDisplayId() {
+ int getDisplayId() {
final DisplayContent displayContent = getDisplayContent();
if (displayContent == null) {
return -1;
@@ -1115,8 +1185,8 @@
}
// Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
// associate them with some stack to enable dimming.
- return mAttrs.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
- && mDisplayContent != null ? mDisplayContent.getHomeStack() : null;
+ final DisplayContent dc = getDisplayContent();
+ return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getHomeStack() : null;
}
/**
@@ -1194,7 +1264,22 @@
@Override
boolean isVisible() {
- if ((mAppToken == null || !mAppToken.hiddenRequested) && isVisibleUnchecked()) {
+ // TODO: The check for hiddenRequested is commented out below, because the window can still
+ // be visible on screen when the flag is true. We would like the isVisible() method to
+ // return an answer closer to if the window is truly visible (can't be an exact answer
+ // without checking the surface state), so comment out the check for now so we can test to
+ // see what problem it causes.
+ // If it doesn't cause any issues, then we can remove just before we lock down the current
+ // release (O) and also consolidate this method with #isVisibleUnchecked() and possibly
+ // other methods like isVisibleNow().
+ // If it does cause problems, then we can look if there are other ways to solve the problem.
+ // If there isn't then uncomment and document here why it is needed.
+ if (/*(mAppToken == null || !mAppToken.hiddenRequested) && */isVisibleUnchecked()
+ // TODO: The window isn't considered visible when the token is hidden, however
+ // uncommenting the check below breaks the visual transition from an app to the launcher
+ // if the home buttons is pressed. Need to investigate an fix that issue before
+ // uncommenting.
+ /* && !mToken.hidden*/) {
// Is this window visible? It is not visible if there is no surface, or we are in the
// process of running an exit animation that will remove the surface, or its app token
// has been hidden.
@@ -1426,7 +1511,7 @@
* Return true if the window is opaque and fully drawn. This indicates
* it may obscure windows behind it.
*/
- boolean isOpaqueDrawn() {
+ private boolean isOpaqueDrawn() {
// When there is keyguard, wallpaper could be placed over the secure app
// window but invisible. We need to check wallpaper visibility explicitly
// to determine if it's occluding apps.
@@ -1493,7 +1578,7 @@
}
changed = true;
if (displayContent != null) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -1557,10 +1642,49 @@
}
/**
+ * If the window has moved due to its containing content frame changing, then notify the
+ * listeners and optionally animate it. Simply checking a change of position is not enough,
+ * because being move due to dock divider is not a trigger for animation.
+ */
+ void handleWindowMovedIfNeeded() {
+ if (!hasMoved()) {
+ return;
+ }
+
+ // Frame has moved, containing content frame has also moved, and we're not currently
+ // animating... let's do something.
+ final int left = mFrame.left;
+ final int top = mFrame.top;
+ final Task task = getTask();
+ final boolean adjustedForMinimizedDockOrIme = task != null
+ && (task.mStack.isAdjustedForMinimizedDockedStack()
+ || task.mStack.isAdjustedForIme());
+ if (mService.okToDisplay()
+ && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+ && !isDragResizing() && !adjustedForMinimizedDockOrIme
+ && (task == null || getTask().mStack.hasMovementAnimations())
+ && !mWinAnimator.mLastHidden) {
+ mWinAnimator.setMoveAnimation(left, top);
+ }
+
+ //TODO (multidisplay): Accessibility supported only for the default display.
+ if (mService.mAccessibilityController != null
+ && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+ mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+ }
+
+ try {
+ mClient.moved(left, top);
+ } catch (RemoteException e) {
+ }
+ mMovedByResize = false;
+ }
+
+ /**
* Return whether this window has moved. (Only makes
* sense to call from performLayoutAndPlaceSurfacesLockedInner().)
*/
- boolean hasMoved() {
+ private boolean hasMoved() {
return mHasSurface && (mContentChanged || mMovedByResize)
&& !mAnimatingExit
&& (mFrame.top != mLastFrame.top || mFrame.left != mLastFrame.left)
@@ -1583,21 +1707,9 @@
&& mFrame.right >= displayInfo.appWidth && mFrame.bottom >= displayInfo.appHeight;
}
+ /** Returns true if last applied config was not yet requested by client. */
boolean isConfigChanged() {
- getMergedConfig(mTmpConfig);
-
- // If the merged configuration is still empty, it means that we haven't issued the
- // configuration to the client yet and we need to return true so the configuration updates.
- boolean configChanged = mMergedConfiguration.equals(Configuration.EMPTY)
- || mTmpConfig.diff(mMergedConfiguration) != 0;
-
- if ((mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
- // Retain configuration changed status until resetConfiguration called.
- mConfigHasChanged |= configChanged;
- configChanged = mConfigHasChanged;
- }
-
- return configChanged;
+ return !mLastReportedConfiguration.equals(getConfiguration());
}
boolean isAdjustedForMinimizedDock() {
@@ -1649,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);
@@ -1718,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.
@@ -1778,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);
}
}
@@ -1809,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);
@@ -1851,13 +1964,24 @@
return 0;
}
+ int getSpecialWindowAnimLayerAdjustment() {
+ int specialAdjustment = 0;
+ if (mIsImWindow) {
+ specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment;
+ } else if (mIsWallpaper) {
+ specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment();
+ }
+
+ return mLayer + specialAdjustment;
+ }
+
void scheduleAnimationIfDimming() {
- if (mDisplayContent == null) {
+ final DisplayContent dc = getDisplayContent();
+ if (dc == null) {
return;
}
final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- if (dimLayerUser != null && mDisplayContent.mDimLayerController.isDimming(
- dimLayerUser, mWinAnimator)) {
+ if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
// Force an animation pass just to update the mDimLayer layer.
mService.scheduleAnimationLocked();
}
@@ -1970,16 +2094,17 @@
return;
}
+ final DisplayContent dc = getDisplayContent();
if (!mAnimatingExit && mAppDied) {
// If app died visible, apply a dim over the window to indicate that it's inactive
- mDisplayContent.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
+ dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
} else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
- && mDisplayContent != null && !mAnimatingExit && isVisibleUnchecked()) {
- mDisplayContent.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
+ && dc != null && !mAnimatingExit && isVisibleUnchecked()) {
+ dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
}
}
- DimLayer.DimLayerUser getDimLayerUser() {
+ private DimLayer.DimLayerUser getDimLayerUser() {
Task task = getTask();
if (task != null) {
return task;
@@ -2038,8 +2163,9 @@
}
void setDisplayLayoutNeeded() {
- if (mDisplayContent != null) {
- mDisplayContent.layoutNeeded = true;
+ final DisplayContent dc = getDisplayContent();
+ if (dc != null) {
+ dc.setLayoutNeeded();
}
}
@@ -2132,10 +2258,10 @@
mTurnOnScreen = true;
}
if (isConfigChanged()) {
- final Configuration newConfig = updateConfiguration();
+ outConfig.setTo(getConfiguration());
if (DEBUG_CONFIGURATION) Slog.i(TAG, "Window " + this + " visible with new config: "
- + newConfig);
- outConfig.setTo(newConfig);
+ + outConfig);
+ mLastReportedConfiguration.setTo(outConfig);
}
}
@@ -2425,7 +2551,7 @@
clearAnimatingWithSavedSurface();
mDestroying = true;
mWinAnimator.hide("stopUsingSavedSurface");
- mService.mWallpaperControllerLocked.hideWallpapers(this);
+ getDisplayContent().mWallpaperController.hideWallpapers(this);
}
void markSavedSurfaceExiting() {
@@ -2503,6 +2629,8 @@
return unfrozeWindows;
}
+ mAppFreezing = false;
+
if (mHasSurface && !mOrientationChanging
&& mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "set mOrientationChanging of " + this);
@@ -2758,18 +2886,19 @@
@Override
public boolean isDimming() {
final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- return dimLayerUser != null && mDisplayContent != null &&
- mDisplayContent.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
+ final DisplayContent dc = getDisplayContent();
+ return dimLayerUser != null && dc != null
+ && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
}
- public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
+ void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
mShowToOwnerOnly = showToOwnerOnly;
}
boolean isHiddenFromUserLocked() {
// Child windows are evaluated based on their parent window.
final WindowState win = getTopParentWindow();
- if (win.mAttrs.type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
+ if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
&& win.mAppToken != null && win.mAppToken.showForAllUsers) {
// All window frames that are fullscreen extend above status bar, but some don't extend
@@ -2808,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;
}
@@ -2817,7 +2945,7 @@
cropRegionToStackBoundsIfNeeded(outRegion);
}
- void cropRegionToStackBoundsIfNeeded(Region region) {
+ private void cropRegionToStackBoundsIfNeeded(Region region) {
final Task task = getTask();
if (task == null || !task.cropWindowsToStackBounds()) {
return;
@@ -2832,18 +2960,11 @@
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).
*/
- public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
+ void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
try {
mClient.windowFocusChanged(focused, inTouchMode);
} catch (RemoteException e) {
@@ -2865,35 +2986,13 @@
}
}
- /**
- * Update our current configurations, based on task configuration.
- *
- * @return A configuration suitable for sending to the client.
- */
- private Configuration updateConfiguration() {
- final boolean configChanged = isConfigChanged();
- getMergedConfig(mMergedConfiguration);
- mConfigHasChanged = false;
- if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION) && configChanged) {
- Slog.i(TAG, "Sending new config to window " + this + ": " +
- " / mergedConfig=" + mMergedConfiguration);
- }
- return mMergedConfiguration;
- }
-
- private void getMergedConfig(Configuration outConfig) {
+ @Override
+ public Configuration getConfiguration() {
if (mAppToken != null && mAppToken.mFrozenMergedConfig.size() > 0) {
- outConfig.setTo(mAppToken.mFrozenMergedConfig.peek());
- return;
+ return mAppToken.mFrozenMergedConfig.peek();
}
- final Task task = getTask();
- final Configuration overrideConfig = task != null
- ? task.mOverrideConfig
- : Configuration.EMPTY;
- outConfig.setTo(mService.mGlobalConfiguration);
- if (overrideConfig != Configuration.EMPTY) {
- outConfig.updateFrom(overrideConfig);
- }
+
+ return super.getConfiguration();
}
void reportResized() {
@@ -2901,8 +3000,14 @@
try {
if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + this
+ ": " + mCompatFrame);
- final Configuration newConfig = isConfigChanged() ? updateConfiguration() : null;
- if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING)
+ final Configuration newConfig;
+ if (isConfigChanged()) {
+ newConfig = new Configuration(getConfiguration());
+ mLastReportedConfiguration.setTo(newConfig);
+ } else {
+ newConfig = null;
+ }
+ if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
final Rect frame = mFrame;
@@ -2911,7 +3016,7 @@
final Rect visibleInsets = mLastVisibleInsets;
final Rect stableInsets = mLastStableInsets;
final Rect outsets = mLastOutsets;
- final boolean reportDraw = mWinAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING;
+ final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING;
if (mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
&& mClient instanceof IWindow.Stub) {
// To prevent deadlock simulate one-way call if win.mClient is a local object.
@@ -2932,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();
}
@@ -2969,7 +3073,7 @@
if (StackId.useWindowFrameForBackdrop(getStackId()) || !resizing) {
return frame;
}
- DisplayInfo displayInfo = getDisplayInfo();
+ final DisplayInfo displayInfo = getDisplayInfo();
mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
return mTmpRect;
}
@@ -3078,7 +3182,7 @@
* @return Whether we reported "resize while not drag resizing" to the application.
* @see #isResizedWhileNotDragResizing()
*/
- boolean isResizedWhileNotDragResizingReported() {
+ private boolean isResizedWhileNotDragResizingReported() {
return mResizedWhileNotDragResizingReported;
}
@@ -3086,7 +3190,7 @@
return mResizeMode;
}
- boolean computeDragResizing() {
+ private boolean computeDragResizing() {
final Task task = getTask();
if (task == null) {
return false;
@@ -3104,7 +3208,7 @@
// and the bounds we clip this window to might be different. In order to avoid holes, we
// simulate that we are still resizing so the app fills the hole with the resizing
// background.
- return (mDisplayContent.mDividerControllerLocked.isResizing()
+ return (getDisplayContent().mDividerControllerLocked.isResizing()
|| mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
!task.inFreeformWorkspace() && !isGoneForLayoutLw();
@@ -3120,7 +3224,7 @@
if (task != null && task.isDragResizing()) {
mResizeMode = task.getDragResizeMode();
} else {
- mResizeMode = mDragResizing && mDisplayContent.mDividerControllerLocked.isResizing()
+ mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
? DRAG_RESIZE_MODE_DOCKED_DIVIDER
: DRAG_RESIZE_MODE_FREEFORM;
}
@@ -3140,9 +3244,6 @@
if (stack != null) {
pw.print(" stackId="); pw.print(stack.mStackId);
}
- if (mNotOnAppsDisplay) {
- pw.print(" mNotOnAppsDisplay="); pw.print(mNotOnAppsDisplay);
- }
pw.print(" mSession="); pw.print(mSession);
pw.print(" mClient="); pw.println(mClient.asBinder());
pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid);
@@ -3223,7 +3324,9 @@
getTouchableRegion(region);
pw.print(prefix); pw.print("touchable region="); pw.println(region);
}
- pw.print(prefix); pw.print("mMergedConfiguration="); pw.println(mMergedConfiguration);
+ pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration());
+ pw.print(prefix); pw.print("mLastReportedConfiguration=");
+ pw.println(mLastReportedConfiguration);
}
pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
@@ -3526,9 +3629,10 @@
}
void requestUpdateWallpaperIfNeeded() {
- if (mDisplayContent != null && (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- mDisplayContent.layoutNeeded = true;
+ final DisplayContent dc = getDisplayContent();
+ if (dc != null && (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+ dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ dc.setLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
}
@@ -3554,12 +3658,12 @@
return winY;
}
- void transferDimToReplacement() {
+ private void transferDimToReplacement() {
final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- if (dimLayerUser != null && mDisplayContent != null) {
- mDisplayContent.mDimLayerController.applyDim(dimLayerUser,
- mReplacementWindow.mWinAnimator,
- (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? true : false);
+ final DisplayContent dc = getDisplayContent();
+ if (dimLayerUser != null && dc != null) {
+ dc.mDimLayerController.applyDim(dimLayerUser,
+ mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
}
}
@@ -3646,7 +3750,7 @@
// want to make sure to do a layout. If called from within the transaction
// loop, this will cause it to restart with a new layout.
if (displayContent != null) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
}
@@ -3657,13 +3761,13 @@
}
if (mAttrs.type == TYPE_INPUT_METHOD) {
- mDisplayContent.mDividerControllerLocked.resetImeHideRequested();
+ getDisplayContent().mDividerControllerLocked.resetImeHideRequested();
}
return true;
}
- void logPerformShow(String prefix) {
+ private void logPerformShow(String prefix) {
if (DEBUG_VISIBILITY
|| (DEBUG_STARTING_WINDOW && mAttrs.type == TYPE_APPLICATION_STARTING)) {
Slog.v(TAG, prefix + this
@@ -3673,11 +3777,11 @@
+ " during animation: policyVis=" + mPolicyVisibility
+ " parentHidden=" + isParentWindowHidden()
+ " tok.hiddenRequested="
- + (mAppToken != null ? mAppToken.hiddenRequested : false)
- + " tok.hidden=" + (mAppToken != null ? mAppToken.hidden : false)
+ + (mAppToken != null && mAppToken.hiddenRequested)
+ + " tok.hidden=" + (mAppToken != null && mAppToken.hidden)
+ " animating=" + mWinAnimator.mAnimating
+ " tok animating="
- + (mWinAnimator.mAppAnimator != null ? mWinAnimator.mAppAnimator.animating : false)
+ + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating)
+ " Callers=" + Debug.getCallers(4));
}
}
@@ -3735,20 +3839,15 @@
}
@Override
- int rebuildWindowList(DisplayContent dc, int addIndex) {
- final DisplayContent winDisplayContent = getDisplayContent();
- if (winDisplayContent == dc || winDisplayContent == null) {
- mDisplayContent = dc;
- return reAddWindow(addIndex);
- }
- return addIndex;
+ int rebuildWindowList(int addIndex) {
+ return reAddWindow(addIndex);
}
// TODO: come-up with a better name for this method that represents what it does.
// 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();
@@ -3759,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;
@@ -3885,7 +3958,7 @@
}
}
mAnimatingExit = false;
- mService.mWallpaperControllerLocked.hideWallpapers(this);
+ getDisplayContent().mWallpaperController.hideWallpapers(this);
}
boolean clearAnimatingFlags() {
@@ -3928,7 +4001,7 @@
}
public boolean isRtl() {
- return mMergedConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
void hideWallpaperWindow(boolean wasDeferred, String reason) {
@@ -3952,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 cbb5040..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;
@@ -482,7 +481,7 @@
// Upon completion of a not-visible to visible status bar animation a relayout is
// required.
if (displayContent != null) {
- displayContent.layoutNeeded = true;
+ displayContent.setLayoutNeeded();
}
}
@@ -966,7 +965,7 @@
mDtDy = tmpFloats[Matrix.MSCALE_Y];
float x = tmpFloats[Matrix.MTRANS_X];
float y = tmpFloats[Matrix.MTRANS_Y];
- mWin.mShownPosition.set((int) x, (int) y);
+ mWin.mShownPosition.set(Math.round(x), Math.round(y));
// Now set the alpha... but because our current hardware
// can't do alpha transformation on a non-opaque surface,
@@ -1059,7 +1058,7 @@
mDtDy = tmpFloats[Matrix.MSCALE_Y];
float x = tmpFloats[Matrix.MTRANS_X];
float y = tmpFloats[Matrix.MTRANS_Y];
- mWin.mShownPosition.set((int) x, (int) y);
+ mWin.mShownPosition.set(Math.round(x), Math.round(y));
mShownAlpha = mAlpha;
} else {
@@ -1350,7 +1349,7 @@
// If we are undergoing seamless rotation, the surface has already
// been set up to persist at it's old location. We need to freeze
// updates until a resize occurs.
- w.mSeamlesslyRotated = w.mSeamlesslyRotated && !mSurfaceResized;
+ mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized);
calculateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect);
@@ -1750,7 +1749,7 @@
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
- mWin.mDisplayContent.adjustForImeIfNeeded();
+ mWin.getDisplayContent().adjustForImeIfNeeded();
if (isEntrance) {
mWin.setDisplayLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
@@ -1936,24 +1935,8 @@
// Compute a transform matrix to undo the coordinate space transformation,
// and present the window at the same physical position it previously occupied.
final int deltaRotation = DisplayContent.deltaRotation(newRotation, oldRotation);
- switch (deltaRotation) {
- case Surface.ROTATION_0:
- transform.reset();
- break;
- case Surface.ROTATION_270:
- transform.setRotate(270, 0, 0);
- transform.postTranslate(0, displayHeight);
- transform.postTranslate(y, 0);
- break;
- case Surface.ROTATION_180:
- transform.reset();
- break;
- case Surface.ROTATION_90:
- transform.setRotate(90, 0, 0);
- transform.postTranslate(displayWidth, 0);
- transform.postTranslate(-y, x);
- break;
- }
+ DisplayContent.createRotationMatrix(deltaRotation, x, y, displayWidth, displayHeight,
+ transform);
// We have two cases:
// 1. Windows with NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY:
@@ -1991,7 +1974,7 @@
cropRect.set(0, 0, w.mRequestedWidth, w.mRequestedWidth + w.mRequestedHeight);
mSurfaceController.setCropInTransaction(cropRect, false);
} else {
- w.mSeamlesslyRotated = true;
+ mService.markForSeamlessRotation(w, true);
transform.getValues(mService.mTmpFloats);
float DsDx = mService.mTmpFloats[Matrix.MSCALE_X];
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 6d10c5a..04e00c4 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -9,7 +9,17 @@
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
@@ -25,6 +35,7 @@
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
@@ -85,7 +96,7 @@
public WindowSurfacePlacer(WindowManagerService service) {
mService = service;
- mWallpaperControllerLocked = mService.mWallpaperControllerLocked;
+ mWallpaperControllerLocked = mService.mRoot.mWallpaperController;
}
/**
@@ -168,7 +179,7 @@
mInLayout = false;
- if (mService.mRoot.layoutNeeded()) {
+ if (mService.mRoot.isLayoutNeeded()) {
if (++mLayoutRepeatCount < 6) {
requestTraversal();
} else {
@@ -204,10 +215,10 @@
final void performLayoutLockedInner(final DisplayContent displayContent,
boolean initial, boolean updateInputWindows) {
- if (!displayContent.layoutNeeded) {
+ if (!displayContent.isLayoutNeeded()) {
return;
}
- displayContent.layoutNeeded = false;
+ displayContent.clearLayoutNeeded();
WindowList windows = displayContent.getWindowList();
boolean isDefaultDisplay = displayContent.isDefaultDisplay;
@@ -215,25 +226,17 @@
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
- if (mService.mInputConsumer != null) {
- mService.mInputConsumer.layout(dw, dh);
- }
-
- if (mService.mWallpaperInputConsumer != null) {
- mService.mWallpaperInputConsumer.layout(dw, dh);
- }
-
final int N = windows.size();
int i;
if (DEBUG_LAYOUT) {
Slog.v(TAG, "-------------------------------------");
- Slog.v(TAG, "performLayout: needed="
- + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh);
+ Slog.v(TAG, "performLayout: needed=" + displayContent.isLayoutNeeded()
+ + " dw=" + dw + " dh=" + dh);
}
mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation,
- mService.mGlobalConfiguration.uiMode);
+ displayContent.getConfiguration().uiMode);
if (isDefaultDisplay) {
// Not needed on non-default displays.
mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
@@ -369,6 +372,7 @@
}
// Window frames may have changed. Tell the input dispatcher about it.
+ mService.mInputMonitor.layoutInputConsumers(dw, dh);
mService.mInputMonitor.setUpdateInputWindowsNeededLw();
if (updateInputWindows) {
mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
@@ -379,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;
@@ -404,8 +407,7 @@
mService.mRoot.mWallpaperMayChange = false;
- // The top-most window will supply the layout params,
- // and we will determine it below.
+ // The top-most window will supply the layout params, and we will determine it below.
LayoutParams animLp = null;
int bestAnimLayer = -1;
boolean fullscreenAnim = false;
@@ -414,21 +416,19 @@
int i;
for (i = 0; i < appsCount; i++) {
final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
- // Clearing the mAnimatingExit flag before entering animation. It's set to
- // true if app window is removed, or window relayout to invisible.
- // This also affects window visibility. We need to clear it *before*
- // maybeUpdateTransitToWallpaper() as the transition selection depends on
- // wallpaper target visibility.
+ // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
+ // window is removed, or window relayout to invisible. This also affects window
+ // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
+ // transition selection depends on wallpaper target visibility.
wtoken.clearAnimatingFlags();
}
+
// Adjust wallpaper before we pull the lower/upper target, since pending changes
// (like the clearAnimatingFlags() above) might affect wallpaper target result.
- if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
- mWallpaperControllerLocked.adjustWallpaperWindows()) {
- mService.mLayersController.assignLayersLocked(windows);
- displayContent.layoutNeeded = true;
- }
+ // Or, the opening app window should be a wallpaper target.
+ mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(displayContent,
+ mService.mOpeningApps);
final WindowState lowerWallpaperTarget =
mWallpaperControllerLocked.getLowerWallpaperTarget();
@@ -446,15 +446,11 @@
upperWallpaperAppToken = upperWallpaperTarget.mAppToken;
}
- // Do a first pass through the tokens for two
- // things:
- // (1) Determine if both the closing and opening
- // app token sets are wallpaper targets, in which
- // case special animations are needed
- // (since the wallpaper needs to stay static
- // behind them).
- // (2) Find the layout params of the top-most
- // application window in the tokens, which is
+ // Do a first pass through the tokens for two things:
+ // (1) Determine if both the closing and opening app token sets are wallpaper targets, in
+ // which case special animations are needed (since the wallpaper needs to stay static behind
+ // them).
+ // (2) Find the layout params of the top-most application window in the tokens, which is
// what will control the animation theme.
final int closingAppsCount = mService.mClosingApps.size();
appsCount = closingAppsCount + mService.mOpeningApps.size();
@@ -495,10 +491,8 @@
transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper,
closingAppHasWallpaper, lowerWallpaperTarget, upperWallpaperTarget);
- // If all closing windows are obscured, then there is
- // no need to do an animation. This is the case, for
- // example, when this transition is being done behind
- // the lock screen.
+ // If all closing windows are obscured, then there is no need to do an animation. This is
+ // the case, for example, when this transition is being done behind the lock screen.
if (!mService.mPolicy.allowAppAnimationsLw()) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Animations disallowed by keyguard or dream.");
@@ -532,12 +526,14 @@
// This has changed the visibility of windows, so perform
// a new layout to get them all up-to-date.
- displayContent.layoutNeeded = true;
+ 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*/);
@@ -720,9 +716,8 @@
WindowState upperWallpaperTarget) {
// if wallpaper is animating in or out set oldWallpaper to null else to wallpaper
final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
- final WindowState oldWallpaper =
- mWallpaperControllerLocked.isWallpaperTargetAnimating()
- ? null : wallpaperTarget;
+ final WindowState oldWallpaper = mWallpaperControllerLocked.isWallpaperTargetAnimating()
+ ? null : wallpaperTarget;
final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
@@ -734,18 +729,17 @@
+ ", closingApps=" + closingApps);
mService.mAnimateWallpaperWithTarget = false;
if (closingAppHasWallpaper && openingAppHasWallpaper) {
- if (DEBUG_APP_TRANSITIONS)
- Slog.v(TAG, "Wallpaper animation!");
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
switch (transit) {
- case AppTransition.TRANSIT_ACTIVITY_OPEN:
- case AppTransition.TRANSIT_TASK_OPEN:
- case AppTransition.TRANSIT_TASK_TO_FRONT:
- transit = AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
+ case TRANSIT_ACTIVITY_OPEN:
+ case TRANSIT_TASK_OPEN:
+ case TRANSIT_TASK_TO_FRONT:
+ transit = TRANSIT_WALLPAPER_INTRA_OPEN;
break;
- case AppTransition.TRANSIT_ACTIVITY_CLOSE:
- case AppTransition.TRANSIT_TASK_CLOSE:
- case AppTransition.TRANSIT_TASK_TO_BACK:
- transit = AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
+ case TRANSIT_ACTIVITY_CLOSE:
+ case TRANSIT_TASK_CLOSE:
+ case TRANSIT_TASK_TO_BACK:
+ transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
break;
}
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
@@ -754,17 +748,15 @@
&& !openingApps.contains(oldWallpaper.mAppToken)
&& closingApps.contains(oldWallpaper.mAppToken)) {
// We are transitioning from an activity with a wallpaper to one without.
- transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "New transit away from wallpaper: "
+ transit = TRANSIT_WALLPAPER_CLOSE;
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
openingApps.contains(wallpaperTarget.mAppToken)) {
// We are transitioning from an activity without
// a wallpaper to now showing the wallpaper
- transit = AppTransition.TRANSIT_WALLPAPER_OPEN;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "New transit into wallpaper: "
+ transit = TRANSIT_WALLPAPER_OPEN;
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else {
mService.mAnimateWallpaperWithTarget = true;
@@ -773,7 +765,7 @@
}
private void processApplicationsAnimatingInPlace(int transit) {
- if (transit == AppTransition.TRANSIT_TASK_IN_PLACE) {
+ if (transit == TRANSIT_TASK_IN_PLACE) {
// Find the focused window
final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
if (win != null) {
@@ -840,13 +832,14 @@
Rect appRect = win != null ? win.getContentFrameLw() :
new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
Rect insets = win != null ? win.mContentInsets : null;
+ final Configuration displayConfig = displayContent.getConfiguration();
// For the new aspect-scaled transition, we want it to always show
// above the animating opening/closing window, and we want to
// synchronize its thumbnail surface with the surface for the
// open/close animation (only on the way down)
anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
- insets, thumbnailHeader, taskId, mService.mGlobalConfiguration.uiMode,
- mService.mGlobalConfiguration.orientation);
+ insets, thumbnailHeader, taskId, displayConfig.uiMode,
+ displayConfig.orientation);
openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
openingAppAnimator.deferThumbnailDestruction =
!mService.mAppTransition.isNextThumbnailTransitionScaleUp();
@@ -897,6 +890,6 @@
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "mTraversalScheduled=" + mTraversalScheduled);
pw.println(prefix + "mHoldScreenWindow=" + mService.mRoot.mHoldScreenWindow);
- pw.println(prefix + "mObsuringWindow=" + mService.mRoot.mObsuringWindow);
+ pw.println(prefix + "mObscuringWindow=" + mService.mRoot.mObscuringWindow);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7ed8e78..afcdc41 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,15 +16,16 @@
package com.android.server.wm;
-import android.annotation.CallSuper;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import android.view.DisplayInfo;
import java.io.PrintWriter;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM;
@@ -78,12 +79,16 @@
// windows will be put to the bottom of the list.
boolean sendingToBottom;
- WindowToken(WindowManagerService service, IBinder _token, int type, boolean _explicit) {
+ // The display this token is on.
+ private DisplayContent mDisplayContent;
+
+ WindowToken(WindowManagerService service, IBinder _token, int type, boolean _explicit,
+ DisplayContent dc) {
mService = service;
token = _token;
windowType = type;
explicit = _explicit;
- mService.mTokenMap.put(token, this);
+ onDisplayChanged(dc);
}
void removeAllWindows() {
@@ -105,15 +110,11 @@
final int count = mChildren.size();
boolean changed = false;
boolean delayed = false;
- DisplayContent displayContent = null;
for (int i = 0; i < count; i++) {
final WindowState win = mChildren.get(i);
if (win.mWinAnimator.isAnimationSet()) {
delayed = true;
- // TODO: This is technically wrong as a token can have windows on multi-displays
- // currently. That will change moving forward though.
- displayContent = win.getDisplayContent();
}
changed |= win.onSetAppExiting();
}
@@ -125,8 +126,8 @@
mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /*updateInputWindows*/);
}
- if (delayed && displayContent != null) {
- displayContent.mExitingTokens.add(this);
+ if (delayed) {
+ mDisplayContent.mExitingTokens.add(this);
}
}
@@ -139,7 +140,7 @@
highestAnimLayer = winHighestAnimLayer;
}
if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) {
- mService.mLayersController.setInputMethodAnimLayerAdjustment(adj);
+ mDisplayContent.setInputMethodAnimLayerAdjustment(adj);
}
}
return highestAnimLayer;
@@ -170,25 +171,41 @@
void addWindow(final WindowState win) {
if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
- final DisplayContent dc = win.getDisplayContent();
if (!win.isChildWindow()) {
int tokenWindowsPos = 0;
- if (dc != null) {
- if (asAppWindowToken() != null) {
- tokenWindowsPos = dc.addAppWindowToWindowList(win);
- } else {
- dc.addNonAppWindowToWindowList(win);
- }
+ if (asAppWindowToken() != null) {
+ tokenWindowsPos = mDisplayContent.addAppWindowToWindowList(win);
+ } else {
+ mDisplayContent.addNonAppWindowToWindowList(win);
}
if (!mChildren.contains(win)) {
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
addChild(win, tokenWindowsPos);
}
- } else if (dc != null) {
- dc.addChildWindowToWindowList(win);
+ } else {
+ mDisplayContent.addChildWindowToWindowList(win);
}
}
+ void addImeWindow(WindowState win) {
+ int pos = mDisplayContent.findDesiredInputMethodWindowIndex(true);
+
+ if (pos < 0) {
+ addWindow(win);
+ mDisplayContent.moveInputMethodDialogs(pos);
+ return;
+ }
+
+ if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Adding input method window " + win + " at " + pos);
+ mDisplayContent.addToWindowList(win, pos);
+ if (!mChildren.contains(win)) {
+ addChild(win, null);
+ }
+ mService.mWindowsChanged = true;
+ mDisplayContent.moveInputMethodDialogs(pos + 1);
+ }
+
/** Return the first window in the token window list that isn't a starting window or null. */
WindowState getFirstNonStartingWindow() {
final int count = mChildren.size();
@@ -218,6 +235,18 @@
return null;
}
+ /** Return true if this token has a window that wants the wallpaper displayed behind it. */
+ boolean windowsCanBeWallpaperTarget() {
+ for (int j = mChildren.size() - 1; j >= 0; j--) {
+ final WindowState w = mChildren.get(j);
+ if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
void hideWallpaperToken(boolean wasDeferred, String reason) {
for (int j = mChildren.size() - 1; j >= 0; j--) {
final WindowState wallpaper = mChildren.get(j);
@@ -240,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)) {
@@ -254,14 +283,18 @@
}
}
- void updateWallpaperVisibility(int dw, int dh, boolean visible, DisplayContent displayContent) {
+ void updateWallpaperVisibility(boolean visible) {
+ final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+ final int dw = displayInfo.logicalWidth;
+ final int dh = displayInfo.logicalHeight;
+
if (hidden == visible) {
hidden = !visible;
// Need to do a layout to ensure the wallpaper now has the correct size.
- displayContent.layoutNeeded = true;
+ 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) {
@@ -281,10 +314,10 @@
"Wallpaper token " + token + " hidden=" + !visible);
hidden = !visible;
// Need to do a layout to ensure the wallpaper now has the correct size.
- mService.getDefaultDisplayContentLocked().layoutNeeded = true;
+ 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);
@@ -388,6 +421,35 @@
return null;
}
+ DisplayContent getDisplayContent() {
+ return mDisplayContent;
+ }
+
+ @Override
+ void 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) {
+ if (mDisplayContent == dc) {
+ return;
+ }
+
+ if (mDisplayContent != null) {
+ mDisplayContent.removeWindowToken(token);
+ }
+ mDisplayContent = dc;
+ mDisplayContent.setWindowToken(token, this);
+
+ super.onDisplayChanged(dc);
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("windows="); pw.println(mChildren);
pw.print(prefix); pw.print("windowType="); pw.print(windowType);
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 9459517..2c46413 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -7,7 +7,6 @@
LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \
- $(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
$(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
$(LOCAL_REL_DIR)/com_android_server_HardwarePropertiesManagerService.cpp \
@@ -65,4 +64,12 @@
libEGL \
libGLESv2 \
libnetutils \
-
+ libhidl \
+ libhwbinder \
+ libutils \
+ android.hardware.power@1.0 \
+ android.hardware.vibrator@1.0 \
+ android.hardware.light@2.0 \
+ android.hardware.vr@1.0 \
+ android.hardware.audio.common@2.0 \
+ android.hardware.tv.input@1.0 \
diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp
deleted file mode 100644
index d004e30..0000000
--- a/services/core/jni/com_android_server_AssetAtlasService.cpp
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2013 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 "AssetAtlasService"
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android/graphics/GraphicsJNI.h"
-
-#include <android_view_GraphicBuffer.h>
-#include <cutils/log.h>
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
-// Disable warnings for Skia.
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#include <SkCanvas.h>
-#include <SkBitmap.h>
-#pragma GCC diagnostic pop
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-// Defines
-// ----------------------------------------------------------------------------
-
-// Defines how long to wait for the GPU when uploading the atlas
-// This timeout is defined in nanoseconds (see EGL_KHR_fence_sync extension)
-#define FENCE_TIMEOUT 2000000000
-
-// ----------------------------------------------------------------------------
-// Canvas management
-// ----------------------------------------------------------------------------
-
-#define CLEANUP_GL_AND_RETURN(result) \
- if (fence != EGL_NO_SYNC_KHR) eglDestroySyncKHR(display, fence); \
- if (image) eglDestroyImageKHR(display, image); \
- if (texture) glDeleteTextures(1, &texture); \
- if (surface != EGL_NO_SURFACE) eglDestroySurface(display, surface); \
- if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context); \
- eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); \
- eglReleaseThread(); \
- eglTerminate(display); \
- return result;
-
-static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject,
- jobject graphicBuffer, jobject bitmapHandle) {
-
- SkBitmap bitmap;
- GraphicsJNI::getSkBitmap(env, bitmapHandle, &bitmap);
- SkAutoLockPixels alp(bitmap);
-
- // The goal of this method is to copy the bitmap into the GraphicBuffer
- // using the GPU to swizzle the texture content
- sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer));
-
- if (buffer != NULL) {
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (display == EGL_NO_DISPLAY) return JNI_FALSE;
-
- EGLint major;
- EGLint minor;
- if (!eglInitialize(display, &major, &minor)) {
- ALOGW("Could not initialize EGL");
- return JNI_FALSE;
- }
-
- // We're going to use a 1x1 pbuffer surface later on
- // The configuration doesn't really matter for what we're trying to do
- EGLint configAttrs[] = {
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 0,
- EGL_DEPTH_SIZE, 0,
- EGL_STENCIL_SIZE, 0,
- EGL_NONE
- };
- EGLConfig configs[1];
- EGLint configCount;
- if (!eglChooseConfig(display, configAttrs, configs, 1, &configCount)) {
- ALOGW("Could not select EGL configuration");
- eglReleaseThread();
- eglTerminate(display);
- return JNI_FALSE;
- }
- if (configCount <= 0) {
- ALOGW("Could not find EGL configuration");
- eglReleaseThread();
- eglTerminate(display);
- return JNI_FALSE;
- }
-
- // These objects are initialized below but the default "null"
- // values are used to cleanup properly at any point in the
- // initialization sequence
- GLuint texture = 0;
- EGLImageKHR image = EGL_NO_IMAGE_KHR;
- EGLSurface surface = EGL_NO_SURFACE;
- EGLSyncKHR fence = EGL_NO_SYNC_KHR;
-
- EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
- EGLContext context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, attrs);
- if (context == EGL_NO_CONTEXT) {
- ALOGW("Could not create EGL context");
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- // Create the 1x1 pbuffer
- EGLint surfaceAttrs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
- surface = eglCreatePbufferSurface(display, configs[0], surfaceAttrs);
- if (surface == EGL_NO_SURFACE) {
- ALOGW("Could not create EGL surface");
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- if (!eglMakeCurrent(display, surface, surface, context)) {
- ALOGW("Could not change current EGL context");
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- // We use an EGLImage to access the content of the GraphicBuffer
- // The EGL image is later bound to a 2D texture
- EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer();
- EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
- image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
- EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
- if (image == EGL_NO_IMAGE_KHR) {
- ALOGW("Could not create EGL image");
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- glGenTextures(1, &texture);
- glBindTexture(GL_TEXTURE_2D, texture);
- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
- if (glGetError() != GL_NO_ERROR) {
- ALOGW("Could not create/bind texture");
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- // Upload the content of the bitmap in the GraphicBuffer
- glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel());
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
- GL_RGBA, GL_UNSIGNED_BYTE, bitmap.getPixels());
- if (glGetError() != GL_NO_ERROR) {
- ALOGW("Could not upload to texture");
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- // The fence is used to wait for the texture upload to finish
- // properly. We cannot rely on glFlush() and glFinish() as
- // some drivers completely ignore these API calls
- fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
- if (fence == EGL_NO_SYNC_KHR) {
- ALOGW("Could not create sync fence %#x", eglGetError());
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
- // pipeline flush (similar to what a glFlush() would do.)
- EGLint waitStatus = eglClientWaitSyncKHR(display, fence,
- EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
- if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
- ALOGW("Failed to wait for the fence %#x", eglGetError());
- CLEANUP_GL_AND_RETURN(JNI_FALSE);
- }
-
- CLEANUP_GL_AND_RETURN(JNI_TRUE);
- }
-
- return JNI_FALSE;
-}
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-#define FIND_CLASS(var, className) \
- var = env->FindClass(className); \
- LOG_FATAL_IF(! (var), "Unable to find class " className);
-
-#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
- var = env->GetMethodID(clazz, methodName, methodDescriptor); \
- LOG_FATAL_IF(!(var), "Unable to find method " methodName);
-
-const char* const kClassPathName = "com/android/server/AssetAtlasService";
-
-static const JNINativeMethod gMethods[] = {
- { "nUploadAtlas", "(Landroid/view/GraphicBuffer;Landroid/graphics/Bitmap;)Z",
- (void*) com_android_server_AssetAtlasService_upload },
-};
-
-int register_android_server_AssetAtlasService(JNIEnv* env) {
- return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
-}
-
-};
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 795f6aa..a2b79ff 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -34,6 +34,8 @@
namespace android
{
+static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
+
static struct parcel_file_descriptor_offsets_t
{
jclass mClass;
@@ -69,10 +71,13 @@
jobject thiz = (jobject)client_data;
const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device);
- char *manufacturer = usb_device_get_manufacturer_name(device);
- char *product = usb_device_get_product_name(device);
+ char *manufacturer = usb_device_get_manufacturer_name(device,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
+ char *product = usb_device_get_product_name(device,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
int version = usb_device_get_version(device);
- char *serial = usb_device_get_serial(device);
+ char *serial = usb_device_get_serial(device,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
jstring deviceName = env->NewStringUTF(devname);
jstring manufacturerName = AndroidRuntime::NewStringLatin1(env, manufacturer);
@@ -99,7 +104,8 @@
while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
if (desc->bDescriptorType == USB_DT_CONFIG) {
struct usb_config_descriptor *config = (struct usb_config_descriptor *)desc;
- char *name = usb_device_get_string(device, config->iConfiguration);
+ char *name = usb_device_get_string(device, config->iConfiguration,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
jstring configName = AndroidRuntime::NewStringLatin1(env, name);
env->CallVoidMethod(thiz, method_addUsbConfiguration,
@@ -110,7 +116,8 @@
free(name);
} else if (desc->bDescriptorType == USB_DT_INTERFACE) {
struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
- char *name = usb_device_get_string(device, interface->iInterface);
+ char *name = usb_device_get_string(device, interface->iInterface,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
jstring interfaceName = AndroidRuntime::NewStringLatin1(env, name);
env->CallVoidMethod(thiz, method_addUsbInterface,
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 03fbd19..cc52260 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -16,6 +16,9 @@
#define LOG_TAG "VibratorService"
+#include <android/hardware/vibrator/1.0/IVibrator.h>
+#include <android/hardware/vibrator/1.0/types.h>
+
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
@@ -26,32 +29,27 @@
#include <stdio.h>
+using android::hardware::getService;
+using android::hardware::vibrator::V1_0::IVibrator;
+using android::hardware::vibrator::V1_0::Status;
+
namespace android
{
-static hw_module_t *gVibraModule = NULL;
-static vibrator_device_t *gVibraDevice = NULL;
+static sp<IVibrator> mHal;
static void vibratorInit(JNIEnv /* env */, jobject /* clazz */)
{
- if (gVibraModule != NULL) {
+ /* TODO(b/31632518) */
+ if (mHal != nullptr) {
return;
}
-
- int err = hw_get_module(VIBRATOR_HARDWARE_MODULE_ID, (hw_module_t const**)&gVibraModule);
-
- if (err) {
- ALOGE("Couldn't load %s module (%s)", VIBRATOR_HARDWARE_MODULE_ID, strerror(-err));
- } else {
- if (gVibraModule) {
- vibrator_open(gVibraModule, &gVibraDevice);
- }
- }
+ mHal = IVibrator::getService("vibrator");
}
static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
{
- if (gVibraModule && gVibraDevice) {
+ if (mHal != nullptr) {
return JNI_TRUE;
} else {
return JNI_FALSE;
@@ -60,10 +58,10 @@
static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
- if (gVibraDevice) {
- int err = gVibraDevice->vibrator_on(gVibraDevice, timeout_ms);
- if (err != 0) {
- ALOGE("The hw module failed in vibrator_on: %s", strerror(-err));
+ if (mHal != nullptr) {
+ Status retStatus = mHal->on(timeout_ms);
+ if (retStatus == Status::ERR) {
+ ALOGE("vibratorOn command failed.");
}
} else {
ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -72,10 +70,10 @@
static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
- if (gVibraDevice) {
- int err = gVibraDevice->vibrator_off(gVibraDevice);
- if (err != 0) {
- ALOGE("The hw module failed in vibrator_off(): %s", strerror(-err));
+ if (mHal != nullptr) {
+ Status retStatus = mHal->off();
+ if (retStatus == Status::ERR) {
+ ALOGE("vibratorOff command failed.");
}
} else {
ALOGW("Tried to stop vibrating but there is no vibrator device.");
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index ecdc71e..b22d5e7 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "BatteryStatsService"
//#define LOG_NDEBUG 0
+#include <android/hardware/power/1.0/IPower.h>
#include <android_runtime/AndroidRuntime.h>
#include <jni.h>
@@ -26,8 +27,6 @@
#include <cutils/log.h>
#include <utils/misc.h>
#include <utils/Log.h>
-#include <hardware/hardware.h>
-#include <hardware/power.h>
#include <suspend/autosuspend.h>
#include <inttypes.h>
@@ -41,6 +40,14 @@
#include <sys/types.h>
#include <unistd.h>
+using android::hardware::Return;
+using android::hardware::Void;
+using android::hardware::power::V1_0::IPower;
+using android::hardware::power::V1_0::PowerStatePlatformSleepState;
+using android::hardware::power::V1_0::PowerStateVoter;
+using android::hardware::power::V1_0::Status;
+using android::hardware::hidl_vec;
+
namespace android
{
@@ -49,7 +56,7 @@
static bool wakeup_init = false;
static sem_t wakeup_sem;
-extern struct power_module* gPowerModule;
+extern sp<IPower> gPowerHal;
static void wakeup_callback(bool success)
{
@@ -174,86 +181,34 @@
}
static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
- int num_modes = -1;
- char *output = (char*)env->GetDirectBufferAddress(outBuf), *offset = output;
+ char *output = (char*)env->GetDirectBufferAddress(outBuf);
+ char *offset = output;
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
- power_state_platform_sleep_state_t *list;
- size_t *voter_list;
int total_added = -1;
if (outBuf == NULL) {
jniThrowException(env, "java/lang/NullPointerException", "null argument");
- goto error;
+ return -1;
}
- if (!gPowerModule) {
- ALOGE("%s: gPowerModule not loaded", POWER_HARDWARE_MODULE_ID);
- goto error;
+ if (gPowerHal == nullptr) {
+ ALOGE("gPowerHal not loaded");
+ return -1;
}
- if (! (gPowerModule->get_platform_low_power_stats && gPowerModule->get_number_of_platform_modes
- && gPowerModule->get_voter_list)) {
- ALOGE("%s: Missing API", POWER_HARDWARE_MODULE_ID);
- goto error;
- }
+ gPowerHal->getPlatformLowPowerStats(
+ [&offset, &remaining, &total_added](hidl_vec<PowerStatePlatformSleepState> states,
+ Status status) {
+ if (status != Status::SUCCESS)
+ return;
+ for (size_t i = 0; i < states.size(); i++) {
+ int added;
+ const PowerStatePlatformSleepState& state = states[i];
- if (gPowerModule->get_number_of_platform_modes) {
- num_modes = gPowerModule->get_number_of_platform_modes(gPowerModule);
- }
-
- if (num_modes < 1) {
- ALOGE("%s: Platform does not even have one low power mode", POWER_HARDWARE_MODULE_ID);
- goto error;
- }
-
- list = (power_state_platform_sleep_state_t *)calloc(num_modes,
- sizeof(power_state_platform_sleep_state_t));
- if (!list) {
- ALOGE("%s: power_state_platform_sleep_state_t allocation failed", POWER_HARDWARE_MODULE_ID);
- goto error;
- }
-
- voter_list = (size_t *)calloc(num_modes, sizeof(*voter_list));
- if (!voter_list) {
- ALOGE("%s: voter_list allocation failed", POWER_HARDWARE_MODULE_ID);
- goto err_free;
- }
-
- gPowerModule->get_voter_list(gPowerModule, voter_list);
-
- for (int i = 0; i < num_modes; i++) {
- list[i].voters = (power_state_voter_t *)calloc(voter_list[i],
- sizeof(power_state_voter_t));
- if (!list[i].voters) {
- ALOGE("%s: voter_t allocation failed", POWER_HARDWARE_MODULE_ID);
- goto err_free;
- }
- }
-
- if (!gPowerModule->get_platform_low_power_stats(gPowerModule, list)) {
- for (int i = 0; i < num_modes; i++) {
- int added;
-
- added = snprintf(offset, remaining,
- "state_%d name=%s time=%" PRIu64 " count=%" PRIu64 " ",
- i + 1, list[i].name, list[i].residency_in_msec_since_boot,
- list[i].total_transitions);
- if (added < 0) {
- break;
- }
- if (added > remaining) {
- added = remaining;
- }
- offset += added;
- remaining -= added;
- total_added += added;
-
- for (unsigned int j = 0; j < list[i].number_of_voters; j++) {
added = snprintf(offset, remaining,
- "voter_%d name=%s time=%" PRIu64 " count=%" PRIu64 " ",
- j + 1, list[i].voters[j].name,
- list[i].voters[j].total_time_in_msec_voted_for_since_boot,
- list[i].voters[j].total_number_of_times_voted_since_boot);
+ "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
+ i + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
+ state.totalTransitions);
if (added < 0) {
break;
}
@@ -263,27 +218,37 @@
offset += added;
remaining -= added;
total_added += added;
- }
- if (remaining <= 0) {
- /* rewrite NULL character*/
- offset--;
- total_added--;
- ALOGE("%s module: buffer not enough", POWER_HARDWARE_MODULE_ID);
- break;
+ for (size_t j = 0; j < state.voters.size(); j++) {
+ const PowerStateVoter& voter = state.voters[j];
+ added = snprintf(offset, remaining,
+ "voter_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
+ j + 1, voter.name.c_str(),
+ voter.totalTimeInMsecVotedForSinceBoot,
+ voter.totalNumberOfTimesVotedSinceBoot);
+ if (added < 0) {
+ break;
+ }
+ if (added > remaining) {
+ added = remaining;
+ }
+ offset += added;
+ remaining -= added;
+ total_added += added;
+ }
+
+ if (remaining <= 0) {
+ /* rewrite NULL character*/
+ offset--;
+ total_added--;
+ ALOGE("PowerHal: buffer not enough");
+ break;
+ }
}
}
- }
+ );
*offset = 0;
total_added += 1;
-
-err_free:
- for (int i = 0; i < num_modes; i++) {
- free(list[i].voters);
- }
- free(list);
- free(voter_list);
-error:
return total_added;
}
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index c8e3946..e6072bb 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -20,136 +20,105 @@
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
+#include <android/hardware/light/2.0/ILight.h>
+#include <android/hardware/light/2.0/types.h>
#include <utils/misc.h>
#include <utils/Log.h>
-#include <hardware/hardware.h>
-#include <hardware/lights.h>
-
+#include <map>
#include <stdio.h>
-namespace android
-{
+namespace android {
-// These values must correspond with the LIGHT_ID constants in
-// LightsService.java
-enum {
- LIGHT_INDEX_BACKLIGHT = 0,
- LIGHT_INDEX_KEYBOARD = 1,
- LIGHT_INDEX_BUTTONS = 2,
- LIGHT_INDEX_BATTERY = 3,
- LIGHT_INDEX_NOTIFICATIONS = 4,
- LIGHT_INDEX_ATTENTION = 5,
- LIGHT_INDEX_BLUETOOTH = 6,
- LIGHT_INDEX_WIFI = 7,
- LIGHT_COUNT
-};
+using ILight = ::android::hardware::light::V2_0::ILight;
+using Brightness = ::android::hardware::light::V2_0::Brightness;
+using Flash = ::android::hardware::light::V2_0::Flash;
+using Type = ::android::hardware::light::V2_0::Type;
+using LightState = ::android::hardware::light::V2_0::LightState;
-struct Devices {
- light_device_t* lights[LIGHT_COUNT];
-};
+static sp<ILight> gLight;
-static light_device_t* get_device(hw_module_t* module, char const* name)
-{
- int err;
- hw_device_t* device;
- err = module->methods->open(module, name, &device);
- if (err == 0) {
- return (light_device_t*)device;
- } else {
- return NULL;
- }
-}
+static bool validate(jint light, jint flash, jint brightness) {
+ bool valid = true;
-static jlong init_native(JNIEnv* /* env */, jobject /* clazz */)
-{
- int err;
- hw_module_t* module;
- Devices* devices;
-
- devices = (Devices*)malloc(sizeof(Devices));
-
- err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
- if (err == 0) {
- devices->lights[LIGHT_INDEX_BACKLIGHT]
- = get_device(module, LIGHT_ID_BACKLIGHT);
- devices->lights[LIGHT_INDEX_KEYBOARD]
- = get_device(module, LIGHT_ID_KEYBOARD);
- devices->lights[LIGHT_INDEX_BUTTONS]
- = get_device(module, LIGHT_ID_BUTTONS);
- devices->lights[LIGHT_INDEX_BATTERY]
- = get_device(module, LIGHT_ID_BATTERY);
- devices->lights[LIGHT_INDEX_NOTIFICATIONS]
- = get_device(module, LIGHT_ID_NOTIFICATIONS);
- devices->lights[LIGHT_INDEX_ATTENTION]
- = get_device(module, LIGHT_ID_ATTENTION);
- devices->lights[LIGHT_INDEX_BLUETOOTH]
- = get_device(module, LIGHT_ID_BLUETOOTH);
- devices->lights[LIGHT_INDEX_WIFI]
- = get_device(module, LIGHT_ID_WIFI);
- } else {
- memset(devices, 0, sizeof(Devices));
+ if (light < 0 || light >= static_cast<int>(Type::COUNT)) {
+ ALOGE("Invalid light parameter %d.", light);
+ valid = false;
}
- return (jlong)devices;
+ if (flash != static_cast<int>(Flash::NONE) &&
+ flash != static_cast<int>(Flash::TIMED) &&
+ flash != static_cast<int>(Flash::HARDWARE)) {
+ ALOGE("Invalid flash parameter %d.", flash);
+ valid = false;
+ }
+
+ if (brightness != static_cast<int>(Brightness::USER) &&
+ brightness != static_cast<int>(Brightness::SENSOR) &&
+ brightness != static_cast<int>(Brightness::LOW_PERSISTENCE)) {
+ ALOGE("Invalid brightness parameter %d.", brightness);
+ valid = false;
+ }
+
+ return valid;
}
-static void finalize_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr)
-{
- Devices* devices = (Devices*)ptr;
- if (devices == NULL) {
+static void setLight_native(
+ JNIEnv* /* env */,
+ jobject /* clazz */,
+ jint light,
+ jint colorARGB,
+ jint flashMode,
+ jint onMS,
+ jint offMS,
+ jint brightnessMode) {
+
+ if (!validate(light, flashMode, brightnessMode)) {
return;
}
- free(devices);
-}
-
-static void setLight_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr,
- jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS, jint brightnessMode)
-{
- Devices* devices = (Devices*)ptr;
- light_state_t state;
-
- if (light < 0 || light >= LIGHT_COUNT || devices->lights[light] == NULL) {
- return ;
+ // TODO(b/31632518)
+ if (gLight == nullptr) {
+ gLight = ILight::getService("light");
}
- uint32_t version = devices->lights[light]->common.version;
+ if (gLight == nullptr) {
+ ALOGE("LightService unable to get ILight interface.");
+ return;
+ }
- memset(&state, 0, sizeof(light_state_t));
+ Type type = static_cast<Type>(light);
+ Flash flash = static_cast<Flash>(flashMode);
+ Brightness brightness = static_cast<Brightness>(brightnessMode);
- if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
- if (light != LIGHT_INDEX_BACKLIGHT) {
+ LightState state{};
+
+ if (brightnessMode == static_cast<int>(Brightness::LOW_PERSISTENCE)) {
+ if (light != static_cast<int>(Type::BACKLIGHT)) {
ALOGE("Cannot set low-persistence mode for non-backlight device.");
return;
}
- if (version < LIGHTS_DEVICE_API_VERSION_2_0) {
- // HAL impl has not been upgraded to support this.
- return;
- }
+ state.flashMode = Flash::NONE;
} else {
// Only set non-brightness settings when not in low-persistence mode
state.color = colorARGB;
- state.flashMode = flashMode;
- state.flashOnMS = onMS;
- state.flashOffMS = offMS;
+ state.flashMode = flash;
+ state.flashOnMs = onMS;
+ state.flashOffMs = offMS;
}
- state.brightnessMode = brightnessMode;
+ state.brightnessMode = brightness;
{
ALOGD_IF_SLOW(50, "Excessive delay setting light");
- devices->lights[light]->set_light(devices->lights[light], &state);
+ gLight->setLight(type, state);
}
}
static const JNINativeMethod method_table[] = {
- { "init_native", "()J", (void*)init_native },
- { "finalize_native", "(J)V", (void*)finalize_native },
- { "setLight_native", "(JIIIIII)V", (void*)setLight_native },
+ { "setLight_native", "(IIIIII)V", (void*)setLight_native },
};
-int register_android_server_LightsService(JNIEnv *env)
-{
+int register_android_server_LightsService(JNIEnv *env) {
return jniRegisterNativeMethods(env, "com/android/server/lights/LightsService",
method_table, NELEM(method_table));
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index e8d4c58..25e819c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1087,6 +1087,7 @@
method_name,
"([B)V");
env_->CallVoidMethod(object_, method, array);
+ env_->DeleteLocalRef(array);
}
jobject JavaObject::get() {
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 048ef76..b2372a3 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -18,6 +18,7 @@
//#define LOG_NDEBUG 0
+#include <android/hardware/power/1.0/IPower.h>
#include "JNIHelp.h"
#include "jni.h"
@@ -37,6 +38,13 @@
#include "com_android_server_power_PowerManagerService.h"
+using android::hardware::Return;
+using android::hardware::Void;
+using android::hardware::power::V1_0::IPower;
+using android::hardware::power::V1_0::PowerHint;
+using android::hardware::power::V1_0::Feature;
+using android::hardware::hidl_vec;
+
namespace android {
// ----------------------------------------------------------------------------
@@ -48,8 +56,7 @@
// ----------------------------------------------------------------------------
static jobject gPowerManagerServiceObj;
-struct power_module* gPowerModule;
-
+sp<IPower> gPowerHal;
static nsecs_t gLastEventTime[USER_ACTIVITY_EVENT_LAST + 1];
// Throttling interval for user activity calls.
@@ -69,8 +76,8 @@
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) {
// Tell the power HAL when user activity occurs.
- if (gPowerModule && gPowerModule->powerHint) {
- gPowerModule->powerHint(gPowerModule, POWER_HINT_INTERACTION, NULL);
+ if (gPowerHal != nullptr) {
+ gPowerHal->powerHint(PowerHint::INTERACTION, 0);
}
if (gPowerManagerServiceObj) {
@@ -99,16 +106,13 @@
}
// ----------------------------------------------------------------------------
-
+//TODO(b/31632518)
static void nativeInit(JNIEnv* env, jobject obj) {
gPowerManagerServiceObj = env->NewGlobalRef(obj);
- status_t err = hw_get_module(POWER_HARDWARE_MODULE_ID,
- (hw_module_t const**)&gPowerModule);
- if (!err) {
- gPowerModule->init(gPowerModule);
- } else {
- ALOGE("Couldn't load %s module (%s)", POWER_HARDWARE_MODULE_ID, strerror(-err));
+ gPowerHal = IPower::getService("power");
+ if (gPowerHal == nullptr) {
+ ALOGE("Couldn't load PowerHAL module");
}
}
@@ -123,13 +127,13 @@
}
static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
- if (gPowerModule) {
+ if (gPowerHal != nullptr) {
if (enable) {
ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(true) while turning screen on");
- gPowerModule->setInteractive(gPowerModule, true);
+ gPowerHal->setInteractive(true);
} else {
ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(false) while turning screen off");
- gPowerModule->setInteractive(gPowerModule, false);
+ gPowerHal->setInteractive(false);
}
}
}
@@ -145,13 +149,11 @@
}
static void nativeSendPowerHint(JNIEnv *env, jclass clazz, jint hintId, jint data) {
- int data_param = data;
-
- if (gPowerModule && gPowerModule->powerHint) {
+ if (gPowerHal != nullptr) {
if(data)
- gPowerModule->powerHint(gPowerModule, (power_hint_t)hintId, &data_param);
+ gPowerHal->powerHint((PowerHint)hintId, data);
else {
- gPowerModule->powerHint(gPowerModule, (power_hint_t)hintId, NULL);
+ gPowerHal->powerHint((PowerHint)hintId, 0);
}
}
}
@@ -159,8 +161,8 @@
static void nativeSetFeature(JNIEnv *env, jclass clazz, jint featureId, jint data) {
int data_param = data;
- if (gPowerModule && gPowerModule->setFeature) {
- gPowerModule->setFeature(gPowerModule, (feature_t)featureId, data_param);
+ if (gPowerHal != nullptr) {
+ gPowerHal->setFeature((Feature)featureId, data_param ? true : false);
}
}
@@ -215,7 +217,7 @@
gLastEventTime[i] = LLONG_MIN;
}
gPowerManagerServiceObj = NULL;
- gPowerModule = NULL;
+ gPowerHal = NULL;
return 0;
}
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index e34a8e8..179fba0 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -24,6 +24,9 @@
#include "JNIHelp.h"
#include "jni.h"
+#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
+#include <android/hardware/tv/input/1.0/ITvInput.h>
+#include <android/hardware/tv/input/1.0/types.h>
#include <gui/Surface.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
@@ -32,6 +35,20 @@
#include <utils/NativeHandle.h>
#include <hardware/tv_input.h>
+using ::android::hardware::audio::common::V2_0::AudioDevice;
+using ::android::hardware::tv::input::V1_0::ITvInput;
+using ::android::hardware::tv::input::V1_0::ITvInputCallback;
+using ::android::hardware::tv::input::V1_0::Result;
+using ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
+using ::android::hardware::tv::input::V1_0::TvInputEvent;
+using ::android::hardware::tv::input::V1_0::TvInputEventType;
+using ::android::hardware::tv::input::V1_0::TvInputType;
+using ::android::hardware::tv::input::V1_0::TvStreamConfig;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+
namespace android {
static struct {
@@ -239,9 +256,9 @@
int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
int removeStream(int deviceId, int streamId);
- const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs);
+ const hidl_vec<TvStreamConfig> getStreamConfigs(int deviceId);
- void onDeviceAvailable(const tv_input_device_info_t& info);
+ void onDeviceAvailable(const TvInputDeviceInfo& info);
void onDeviceUnavailable(int deviceId);
void onStreamConfigurationsChanged(int deviceId);
void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
@@ -263,73 +280,60 @@
class NotifyHandler : public MessageHandler {
public:
- NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event);
- ~NotifyHandler();
+ NotifyHandler(JTvInputHal* hal, const TvInputEvent& event);
virtual void handleMessage(const Message& message);
private:
- tv_input_event_t mEvent;
+ TvInputEvent mEvent;
JTvInputHal* mHal;
};
- JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev, const sp<Looper>& looper);
+ class TvInputCallback : public ITvInputCallback {
+ public:
+ TvInputCallback(JTvInputHal* hal);
+ Return<void> notify(const TvInputEvent& event) override;
+ private:
+ JTvInputHal* mHal;
+ };
- static void notify(
- tv_input_device_t* dev, tv_input_event_t* event, void* data);
-
- static void cloneTvInputEvent(
- tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent);
+ JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper);
Mutex mLock;
jweak mThiz;
- tv_input_device_t* mDevice;
- tv_input_callback_ops_t mCallback;
sp<Looper> mLooper;
KeyedVector<int, KeyedVector<int, Connection> > mConnections;
+
+ sp<ITvInput> mTvInput;
+ sp<ITvInputCallback> mTvInputCallback;
};
-JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device,
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput,
const sp<Looper>& looper) {
mThiz = env->NewWeakGlobalRef(thiz);
- mDevice = device;
- mCallback.notify = &JTvInputHal::notify;
+ mTvInput = tvInput;
mLooper = looper;
-
- mDevice->initialize(mDevice, &mCallback, this);
+ mTvInputCallback = new TvInputCallback(this);
+ mTvInput->setCallback(mTvInputCallback);
}
JTvInputHal::~JTvInputHal() {
- mDevice->common.close((hw_device_t*)mDevice);
-
+ mTvInput->setCallback(nullptr);
JNIEnv* env = AndroidRuntime::getJNIEnv();
env->DeleteWeakGlobalRef(mThiz);
mThiz = NULL;
}
JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
- tv_input_module_t* module = NULL;
- status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID,
- (hw_module_t const**)&module);
- if (err) {
- ALOGE("Couldn't load %s module (%s)",
- TV_INPUT_HARDWARE_MODULE_ID, strerror(-err));
- return 0;
+ // TODO(b/31632518)
+ sp<ITvInput> tvInput = ITvInput::getService("tv.input");
+ if (tvInput == nullptr) {
+ ALOGE("Couldn't get tv.input service.");
+ return nullptr;
}
- tv_input_device_t* device = NULL;
- err = module->common.methods->open(
- (hw_module_t*)module,
- TV_INPUT_DEFAULT_DEVICE,
- (hw_device_t**)&device);
- if (err) {
- ALOGE("Couldn't open %s device (%s)",
- TV_INPUT_DEFAULT_DEVICE, strerror(-err));
- return 0;
- }
-
- return new JTvInputHal(env, thiz, device, looper);
+ return new JTvInputHal(env, thiz, tvInput, looper);
}
int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
@@ -353,16 +357,22 @@
}
if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
// Need to configure stream
- int numConfigs = 0;
- const tv_stream_config_t* configs = NULL;
- if (mDevice->get_stream_configurations(
- mDevice, deviceId, &numConfigs, &configs) != 0) {
- ALOGE("Couldn't get stream configs");
+ Result result = Result::UNKNOWN;
+ hidl_vec<TvStreamConfig> list;
+ mTvInput->getStreamConfigurations(deviceId,
+ [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
+ result = res;
+ if (res == Result::OK) {
+ list = configs;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
return UNKNOWN_ERROR;
}
int configIndex = -1;
- for (int i = 0; i < numConfigs; ++i) {
- if (configs[i].stream_id == streamId) {
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (list[i].streamId == streamId) {
configIndex = i;
break;
}
@@ -371,34 +381,27 @@
ALOGE("Cannot find a config with given stream ID: %d", streamId);
return BAD_VALUE;
}
- connection.mStreamType = configs[configIndex].type;
+ connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
- tv_stream_t stream;
- stream.stream_id = configs[configIndex].stream_id;
- if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
- stream.buffer_producer.width = configs[configIndex].max_video_width;
- stream.buffer_producer.height = configs[configIndex].max_video_height;
- }
- if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) {
- ALOGE("Couldn't add stream");
+ result = Result::UNKNOWN;
+ const native_handle_t* sidebandStream;
+ mTvInput->openStream(deviceId, streamId,
+ [&result, &sidebandStream](Result res, const native_handle_t* handle) {
+ result = res;
+ if (res == Result::OK) {
+ sidebandStream = handle;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
+ result);
return UNKNOWN_ERROR;
}
- if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
- connection.mSourceHandle = NativeHandle::create(
- stream.sideband_stream_source_handle, false);
- } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
- if (connection.mThread != NULL) {
- connection.mThread->shutdown();
- }
- connection.mThread = new BufferProducerThread(mDevice, deviceId, &stream);
- connection.mThread->run("BufferProducerThread");
- }
+ connection.mSourceHandle = NativeHandle::create((native_handle_t*)sidebandStream, false);
}
connection.mSurface = surface;
- if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+ if (connection.mSurface != nullptr) {
connection.mSurface->setSidebandStream(connection.mSourceHandle);
- } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) {
- connection.mThread->setSurface(surface);
}
return NO_ERROR;
}
@@ -421,8 +424,8 @@
connection.mThread->shutdown();
connection.mThread.clear();
}
- if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) {
- ALOGE("Couldn't remove stream");
+ if (mTvInput->closeStream(deviceId, streamId) != Result::OK) {
+ ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
return BAD_VALUE;
}
if (connection.mSourceHandle != NULL) {
@@ -431,41 +434,26 @@
return NO_ERROR;
}
-const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) {
- const tv_stream_config_t* configs = NULL;
- if (mDevice->get_stream_configurations(
- mDevice, deviceId, numConfigs, &configs) != 0) {
- ALOGE("Couldn't get stream configs");
- return NULL;
+const hidl_vec<TvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
+ Result result = Result::UNKNOWN;
+ hidl_vec<TvStreamConfig> list;
+ mTvInput->getStreamConfigurations(deviceId,
+ [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
+ result = res;
+ if (res == Result::OK) {
+ list = configs;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
}
- return configs;
+ return list;
}
-// static
-void JTvInputHal::notify(
- tv_input_device_t* dev, tv_input_event_t* event, void* data) {
- JTvInputHal* thiz = (JTvInputHal*)data;
- thiz->mLooper->sendMessage(new NotifyHandler(thiz, event), event->type);
-}
-
-// static
-void JTvInputHal::cloneTvInputEvent(
- tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent) {
- memcpy(dstEvent, srcEvent, sizeof(tv_input_event_t));
- if ((srcEvent->type == TV_INPUT_EVENT_DEVICE_AVAILABLE ||
- srcEvent->type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE ||
- srcEvent->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) &&
- srcEvent->device_info.audio_address != NULL){
- char* audio_address = new char[strlen(srcEvent->device_info.audio_address) + 1];
- strcpy(audio_address, srcEvent->device_info.audio_address);
- dstEvent->device_info.audio_address = audio_address;
- }
-}
-
-void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) {
+void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) {
{
Mutex::Autolock autoLock(&mLock);
- mConnections.add(info.device_id, KeyedVector<int, Connection>());
+ mConnections.add(info.deviceId, KeyedVector<int, Connection>());
}
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -473,17 +461,20 @@
gTvInputHardwareInfoBuilderClassInfo.clazz,
gTvInputHardwareInfoBuilderClassInfo.constructor);
env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.device_id);
+ builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
env->CallObjectMethod(
builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
- if (info.type == TV_INPUT_TYPE_HDMI) {
+ if (info.type == TvInputType::HDMI) {
env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.hdmi.port_id);
+ builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId);
}
env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audio_type);
- if (info.audio_type != AUDIO_DEVICE_NONE) {
- jstring audioAddress = env->NewStringUTF(info.audio_address);
+ builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType);
+ if (info.audioType != AudioDevice::NONE) {
+ uint8_t buffer[info.audioAddress.size() + 1];
+ memcpy(buffer, info.audioAddress.data(), info.audioAddress.size());
+ buffer[info.audioAddress.size()] = '\0';
+ jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char *>(buffer));
env->CallObjectMethod(
builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
env->DeleteLocalRef(audioAddress);
@@ -556,48 +547,37 @@
}
}
-JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event) {
+JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEvent& event) {
mHal = hal;
- cloneTvInputEvent(&mEvent, event);
-}
-
-JTvInputHal::NotifyHandler::~NotifyHandler() {
- if ((mEvent.type == TV_INPUT_EVENT_DEVICE_AVAILABLE ||
- mEvent.type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE ||
- mEvent.type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) &&
- mEvent.device_info.audio_address != NULL) {
- delete mEvent.device_info.audio_address;
- }
+ mEvent = event;
}
void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
switch (mEvent.type) {
- case TV_INPUT_EVENT_DEVICE_AVAILABLE: {
- mHal->onDeviceAvailable(mEvent.device_info);
+ case TvInputEventType::DEVICE_AVAILABLE: {
+ mHal->onDeviceAvailable(mEvent.deviceInfo);
} break;
- case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: {
- mHal->onDeviceUnavailable(mEvent.device_info.device_id);
+ case TvInputEventType::DEVICE_UNAVAILABLE: {
+ mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
} break;
- case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: {
- mHal->onStreamConfigurationsChanged(mEvent.device_info.device_id);
- } break;
- case TV_INPUT_EVENT_CAPTURE_SUCCEEDED: {
- mHal->onCaptured(mEvent.capture_result.device_id,
- mEvent.capture_result.stream_id,
- mEvent.capture_result.seq,
- true /* succeeded */);
- } break;
- case TV_INPUT_EVENT_CAPTURE_FAILED: {
- mHal->onCaptured(mEvent.capture_result.device_id,
- mEvent.capture_result.stream_id,
- mEvent.capture_result.seq,
- false /* succeeded */);
+ case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
+ mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId);
} break;
default:
ALOGE("Unrecognizable event");
}
}
+JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+ mHal = hal;
+}
+
+Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
+ // TODO(b/32200867): Ensure the event type values are in sync with the framework code.
+ mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
+ return Void();
+}
+
////////////////////////////////////////////////////////////////////////////////
static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) {
@@ -628,22 +608,22 @@
static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
jlong ptr, jint deviceId, jint generation) {
JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
- int numConfigs = 0;
- const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs);
+ const hidl_vec<TvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
- jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL);
- for (int i = 0; i < numConfigs; ++i) {
+ jobjectArray result = env->NewObjectArray(configs.size(), gTvStreamConfigClassInfo.clazz, NULL);
+ for (size_t i = 0; i < configs.size(); ++i) {
jobject builder = env->NewObject(
gTvStreamConfigBuilderClassInfo.clazz,
gTvStreamConfigBuilderClassInfo.constructor);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id);
+ builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].streamId);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type);
+ builder, gTvStreamConfigBuilderClassInfo.type,
+ TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width);
+ builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].maxVideoWidth);
env->CallObjectMethod(
- builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height);
+ builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].maxVideoHeight);
env->CallObjectMethod(
builder, gTvStreamConfigBuilderClassInfo.generation, generation);
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/jni/onload.cpp b/services/core/jni/onload.cpp
index 327019d..d69c37f 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -21,7 +21,6 @@
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
-int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
@@ -76,7 +75,6 @@
register_android_server_location_GnssLocationProvider(env);
register_android_server_location_FlpHardwareProvider(env);
register_android_server_connectivity_Vpn(env);
- register_android_server_AssetAtlasService(env);
register_android_server_ConsumerIrService(env);
register_android_server_BatteryStatsService(env);
register_android_server_hdmi_HdmiCecController(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3d0ca80..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;
@@ -221,6 +224,7 @@
private static final String ATTR_PERMISSION_POLICY = "permission-policy";
private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
"device-provisioning-config-applied";
+ private static final String ATTR_DEVICE_PAIRED = "device-paired";
private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
@@ -301,6 +305,7 @@
private static final int CODE_NONSYSTEM_USER_EXISTS = 5;
private static final int CODE_ACCOUNTS_NOT_EMPTY = 6;
private static final int CODE_NOT_SYSTEM_USER = 7;
+ private static final int CODE_HAS_PAIRED = 8;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, CODE_USER_NOT_RUNNING,
@@ -315,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;
@@ -344,6 +355,11 @@
*/
boolean mHasFeature;
+ /**
+ * Whether or not this device is a watch.
+ */
+ boolean mIsWatch;
+
private final SecurityLogMonitor mSecurityLogMonitor;
private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
@@ -410,20 +426,14 @@
}
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;
int mPasswordOwner = -1;
long mLastMaximumTimeToLock = -1;
boolean mUserSetupComplete = false;
+ boolean mPaired = false;
int mUserProvisioningState;
int mPermissionPolicy;
@@ -582,36 +592,28 @@
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;
- long strongAuthUnlockTimeout = DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ long strongAuthUnlockTimeout = 0; // admin doesn't participate by default
static final int DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE = 0;
int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
@@ -712,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) {
@@ -726,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);
}
}
@@ -952,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(
@@ -1216,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=");
@@ -1615,6 +1625,8 @@
mHasFeature = mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
+ mIsWatch = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WATCH);
if (!mHasFeature) {
// Skip the rest of the initialization
return;
@@ -2215,6 +2227,10 @@
out.attribute(null, ATTR_SETUP_COMPLETE,
Boolean.toString(true));
}
+ if (policy.mPaired) {
+ out.attribute(null, ATTR_DEVICE_PAIRED,
+ Boolean.toString(true));
+ }
if (policy.mDeviceProvisioningConfigApplied) {
out.attribute(null, ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED,
Boolean.toString(true));
@@ -2259,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");
}
@@ -2379,6 +2392,10 @@
if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) {
policy.mUserSetupComplete = true;
}
+ String paired = parser.getAttributeValue(null, ATTR_DEVICE_PAIRED);
+ if (paired != null && Boolean.toString(true).equals(paired)) {
+ policy.mPaired = true;
+ }
String deviceProvisioningConfigApplied = parser.getAttributeValue(null,
ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED);
if (deviceProvisioningConfigApplied != null
@@ -2438,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)) {
@@ -2499,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);
@@ -2613,7 +2616,7 @@
// Register an observer for watching for user setup complete.
new SetupContentObserver(mHandler).register();
// Initialize the user setup state, to handle the upgrade case.
- updateUserSetupComplete();
+ updateUserSetupCompleteAndPaired();
List<String> packageList;
synchronized (this) {
@@ -3101,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());
}
}
@@ -3119,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.
@@ -3128,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;
@@ -3188,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());
}
}
@@ -3206,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.
@@ -3215,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;
@@ -3443,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());
}
}
@@ -3461,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.
@@ -3470,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;
@@ -3484,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());
}
}
@@ -3502,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.
@@ -3511,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;
@@ -3528,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());
}
}
@@ -3546,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.
@@ -3558,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;
@@ -3575,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());
}
}
@@ -3593,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.
@@ -3605,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;
@@ -3622,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());
}
}
@@ -3640,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.
@@ -3652,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;
@@ -3669,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());
}
}
@@ -3687,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.
@@ -3699,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;
@@ -3741,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);
}
@@ -3956,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"
@@ -3975,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;
@@ -4248,10 +4233,15 @@
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkArgument(timeoutMs >= MINIMUM_STRONG_AUTH_TIMEOUT_MS,
- "Timeout must not be lower than the minimum strong auth timeout.");
- Preconditions.checkArgument(timeoutMs <= DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS,
- "Timeout must not be higher than the default strong auth timeout.");
+ Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
+ // timeoutMs with value 0 means that the admin doesn't participate
+ // timeoutMs is clamped to the interval in case the internal constants change in the future
+ if (timeoutMs != 0 && timeoutMs < MINIMUM_STRONG_AUTH_TIMEOUT_MS) {
+ timeoutMs = MINIMUM_STRONG_AUTH_TIMEOUT_MS;
+ }
+ if (timeoutMs > DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+ timeoutMs = DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ }
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
@@ -4267,7 +4257,7 @@
/**
* Return a single admin's strong auth unlock timeout or minimum value (strictest) of all
* admins if who is null.
- * Returns default timeout if not configured.
+ * Returns 0 if not configured for the provided admin.
*/
@Override
public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) {
@@ -4278,9 +4268,7 @@
synchronized (this) {
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userId, parent);
- return admin != null ? Math.max(admin.strongAuthUnlockTimeout,
- MINIMUM_STRONG_AUTH_TIMEOUT_MS)
- : DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ return admin != null ? admin.strongAuthUnlockTimeout : 0;
}
// Return the strictest policy across all participating admins.
@@ -4288,8 +4276,10 @@
long strongAuthUnlockTimeout = DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
for (int i = 0; i < admins.size(); i++) {
- strongAuthUnlockTimeout = Math.min(admins.get(i).strongAuthUnlockTimeout,
- strongAuthUnlockTimeout);
+ final long timeout = admins.get(i).strongAuthUnlockTimeout;
+ if (timeout != 0) { // take only participating admins into account
+ strongAuthUnlockTimeout = Math.min(timeout, strongAuthUnlockTimeout);
+ }
}
return Math.max(strongAuthUnlockTimeout, MINIMUM_STRONG_AUTH_TIMEOUT_MS);
}
@@ -4819,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;
}
@@ -4833,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);
@@ -5868,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();
@@ -6050,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);
@@ -6146,6 +6138,13 @@
return getUserData(userHandle).mUserSetupComplete;
}
+ private boolean hasPaired(int userHandle) {
+ if (!mHasFeature) {
+ return true;
+ }
+ return getUserData(userHandle).mPaired;
+ }
+
@Override
public int getUserProvisioningState() {
if (!mHasFeature) {
@@ -6176,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) !=
@@ -6380,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 "
@@ -6401,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;
@@ -6430,6 +6425,9 @@
case CODE_ACCOUNTS_NOT_EMPTY:
throw new IllegalStateException("Not allowed to set the device owner because there "
+ "are already some accounts on the device");
+ case CODE_HAS_PAIRED:
+ throw new IllegalStateException("Not allowed to set the device owner because this "
+ + "device has already paired");
default:
throw new IllegalStateException("Unknown @DeviceOwnerPreConditionCode " + code);
}
@@ -8181,14 +8179,15 @@
}
/**
- * We need to update the internal state of whether a user has completed setup once. After
- * that, we ignore any changes that reset the Settings.Secure.USER_SETUP_COMPLETE changes
- * as we don't trust any apps that might try to reset it.
+ * We need to update the internal state of whether a user has completed setup or a
+ * device has paired once. After that, we ignore any changes that reset the
+ * Settings.Secure.USER_SETUP_COMPLETE or Settings.Secure.DEVICE_PAIRED change
+ * as we don't trust any apps that might try to reset them.
* <p>
* Unfortunately, we don't know which user's setup state was changed, so we write all of
* them.
*/
- void updateUserSetupComplete() {
+ void updateUserSetupCompleteAndPaired() {
List<UserInfo> users = mUserManager.getUsers(true);
final int N = users.size();
for (int i = 0; i < N; i++) {
@@ -8203,6 +8202,16 @@
}
}
}
+ if (mIsWatch && mInjector.settingsSecureGetIntForUser(Settings.Secure.DEVICE_PAIRED, 0,
+ userHandle) != 0) {
+ DevicePolicyData policy = getUserData(userHandle);
+ if (!policy.mPaired) {
+ policy.mPaired = true;
+ synchronized (this) {
+ saveSettingsLocked(userHandle);
+ }
+ }
+ }
}
}
@@ -8212,6 +8221,7 @@
Settings.Secure.USER_SETUP_COMPLETE);
private final Uri mDeviceProvisioned = Settings.Global.getUriFor(
Settings.Global.DEVICE_PROVISIONED);
+ private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
public SetupContentObserver(Handler handler) {
super(handler);
@@ -8220,12 +8230,15 @@
void register() {
mInjector.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL);
mInjector.registerContentObserver(mDeviceProvisioned, false, this, UserHandle.USER_ALL);
+ if (mIsWatch) {
+ mInjector.registerContentObserver(mPaired, false, this, UserHandle.USER_ALL);
+ }
}
@Override
public void onChange(boolean selfChange, Uri uri) {
- if (mUserSetupComplete.equals(uri)) {
- updateUserSetupComplete();
+ if (mUserSetupComplete.equals(uri) || (mIsWatch && mPaired.equals(uri))) {
+ updateUserSetupCompleteAndPaired();
} else if (mDeviceProvisioned.equals(uri)) {
synchronized (DevicePolicyManagerService.this) {
// Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property
@@ -8375,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);
@@ -8534,15 +8547,16 @@
final PackageManager packageManager = mContext.getPackageManager();
switch (grantState) {
case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED: {
- packageManager.grantRuntimePermission(packageName, permission, user);
+ mInjector.getPackageManagerInternal().grantRuntimePermission(packageName,
+ permission, user.getIdentifier(), true /* override policy */);
packageManager.updatePermissionFlags(permission, packageName,
PackageManager.FLAG_PERMISSION_POLICY_FIXED,
PackageManager.FLAG_PERMISSION_POLICY_FIXED, user);
} break;
case DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED: {
- packageManager.revokeRuntimePermission(packageName,
- permission, user);
+ mInjector.getPackageManagerInternal().revokeRuntimePermission(packageName,
+ permission, user.getIdentifier(), true /* override policy */);
packageManager.updatePermissionFlags(permission, packageName,
PackageManager.FLAG_PERMISSION_POLICY_FIXED,
PackageManager.FLAG_PERMISSION_POLICY_FIXED, user);
@@ -8614,19 +8628,13 @@
}
synchronized (this) {
if (mOwners.hasDeviceOwner()) {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // Only split-system-user systems support managed-profiles in combination with
- // device-owner.
- return false;
- }
- if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
- // Only system device-owner supports managed-profiles. Non-system device-owner
- // doesn't.
- return false;
- }
- if (callingUserId == UserHandle.USER_SYSTEM) {
- // Managed-profiles cannot be setup on the system user, only regular users.
- return false;
+ // STOPSHIP Only allow creating a managed profile if allowed by the device
+ // owner. http://b/31952368
+ if (mInjector.userManagerIsSplitSystemUser()) {
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ // Managed-profiles cannot be setup on the system user.
+ return false;
+ }
}
}
}
@@ -8686,6 +8694,9 @@
if (!mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
return CODE_USER_NOT_RUNNING;
}
+ if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
+ return CODE_HAS_PAIRED;
+ }
if (isAdb) {
// if shell command runs after user setup completed check device status. Otherwise, OK.
if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
@@ -9058,8 +9069,10 @@
if (!isDeviceOwnerManagedSingleUserDevice()) {
mInjector.securityLogSetLoggingEnabledProperty(false);
Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user device.");
- setBackupServiceEnabledInternal(false);
- Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
+ if (mOwners.hasDeviceOwner()) {
+ setBackupServiceEnabledInternal(false);
+ Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
+ }
}
}
@@ -9349,8 +9362,8 @@
return true;
}
synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
try {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
IBackupManager ibm = mInjector.getIBackupManager();
return ibm != null && ibm.isBackupServiceActive(UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
@@ -9435,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 aba4dc0..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";
@@ -732,7 +734,7 @@
traceBeginAndSlog("IpConnectivityMetrics");
mSystemServiceManager.startService(IpConnectivityMetrics.class);
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ traceEnd();
traceBeginAndSlog("PinnerService");
mSystemServiceManager.startService(PinnerService.class);
@@ -747,7 +749,6 @@
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
ILockSettings lockSettings = null;
- AssetAtlasService atlas = null;
MediaRouterService mediaRouter = null;
// Bring up services needed for UI.
@@ -859,12 +860,7 @@
if (!disableNonCoreServices) {
traceBeginAndSlog("StartClipboardService");
- try {
- ServiceManager.addService(Context.CLIPBOARD_SERVICE,
- new ClipboardService(context));
- } catch (Throwable e) {
- reportWtf("starting Clipboard Service", e);
- }
+ mSystemServiceManager.startService(ClipboardService.class);
traceEnd();
}
@@ -1240,17 +1236,6 @@
traceEnd();
}
- if (!disableNonCoreServices && ZygoteInit.PRELOAD_RESOURCES) {
- traceBeginAndSlog("StartAssetAtlasService");
- try {
- atlas = new AssetAtlasService(context);
- ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE, atlas);
- } catch (Throwable e) {
- reportWtf("starting AssetAtlasService", e);
- }
- traceEnd();
- }
-
if (!disableNonCoreServices) {
traceBeginAndSlog("AddGraphicsStatsService");
ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
@@ -1372,12 +1357,9 @@
mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
traceEnd();
- if (Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 0) == 0 ||
- UserManager.isDeviceInDemoMode(mSystemContext)) {
- traceBeginAndSlog("StartRetailDemoModeService");
- mSystemServiceManager.startService(RetailDemoModeService.class);
- traceEnd();
- }
+ traceBeginAndSlog("StartRetailDemoModeService");
+ mSystemServiceManager.startService(RetailDemoModeService.class);
+ traceEnd();
// It is now time to start up the app processes...
@@ -1423,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);
@@ -1473,7 +1455,6 @@
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
- final AssetAtlasService atlasF = atlas;
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
@@ -1591,13 +1572,6 @@
reportWtf("Notifying CommonTimeManagementService running", e);
}
traceEnd();
- traceBeginAndSlog("MakeAtlasServiceReady");
- try {
- if (atlasF != null) atlasF.systemRunning();
- } catch (Throwable e) {
- reportWtf("Notifying AssetAtlasService running", e);
- }
- traceEnd();
traceBeginAndSlog("MakeInputManagerServiceReady");
try {
// TODO(BT) Pass parameter to input manager
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 4c75452..a8356dc 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -283,14 +283,20 @@
mReceiveThread.start();
}
- // Returns seconds since Unix Epoch.
- // TODO: use SystemClock.elapsedRealtime() instead
+ // Returns seconds since device boot.
private static long curTime() {
- return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS;
+ return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
+ }
+
+ public static class InvalidRaException extends Exception {
+ public InvalidRaException(String m) {
+ super(m);
+ }
}
// A class to hold information about an RA.
- private class Ra {
+ @VisibleForTesting
+ class Ra {
// From RFC4861:
private static final int ICMP6_RA_HEADER_LEN = 16;
private static final int ICMP6_RA_CHECKSUM_OFFSET =
@@ -362,7 +368,7 @@
} catch (UnsupportedOperationException e) {
// array() failed. Cannot happen, mPacket is array-backed and read-write.
return "???";
- } catch (ClassCastException | UnknownHostException e) {
+ } catch (ClassCastException|UnknownHostException e) {
// Cannot happen.
return "???";
}
@@ -372,16 +378,16 @@
// TODO: Make this static once RA is its own class.
private void prefixOptionToString(StringBuffer sb, int offset) {
String prefix = IPv6AddresstoString(offset + 16);
- int length = uint8(mPacket.get(offset + 2));
- long valid = mPacket.getInt(offset + 4);
- long preferred = mPacket.getInt(offset + 8);
+ int length = getUint8(mPacket, offset + 2);
+ long valid = getUint32(mPacket, offset + 4);
+ long preferred = getUint32(mPacket, offset + 8);
sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
}
private void rdnssOptionToString(StringBuffer sb, int offset) {
- int optLen = uint8(mPacket.get(offset + 1)) * 8;
+ int optLen = getUint8(mPacket, offset + 1) * 8;
if (optLen < 24) return; // Malformed or empty.
- long lifetime = uint32(mPacket.getInt(offset + 4));
+ long lifetime = getUint32(mPacket, offset + 4);
int numServers = (optLen - 8) / 16;
sb.append("DNS ").append(lifetime).append("s");
for (int server = 0; server < numServers; server++) {
@@ -395,7 +401,7 @@
sb.append(String.format("RA %s -> %s %ds ",
IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
- uint16(mPacket.getShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET))));
+ getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
for (int i: mPrefixOptionOffsets) {
prefixOptionToString(sb, i);
}
@@ -403,7 +409,7 @@
rdnssOptionToString(sb, i);
}
return sb.toString();
- } catch (BufferUnderflowException | IndexOutOfBoundsException e) {
+ } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
return "<Malformed RA>";
}
}
@@ -436,16 +442,20 @@
// Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
// (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
// specifications.
- Ra(byte[] packet, int length) {
+ Ra(byte[] packet, int length) throws InvalidRaException {
+ if (length < ICMP6_RA_OPTION_OFFSET) {
+ throw new InvalidRaException("Not an ICMP6 router advertisement");
+ }
+
mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
mLastSeen = curTime();
// Sanity check packet in case a packet arrives before we attach RA filter
// to our packet socket. b/29586253
if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
- uint8(mPacket.get(IPV6_NEXT_HEADER_OFFSET)) != IPPROTO_ICMPV6 ||
- uint8(mPacket.get(ICMP6_TYPE_OFFSET)) != ICMP6_ROUTER_ADVERTISEMENT) {
- throw new IllegalArgumentException("Not an ICMP6 router advertisement");
+ getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
+ getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMP6_ROUTER_ADVERTISEMENT) {
+ throw new InvalidRaException("Not an ICMP6 router advertisement");
}
@@ -466,8 +476,8 @@
mPacket.position(ICMP6_RA_OPTION_OFFSET);
while (mPacket.hasRemaining()) {
final int position = mPacket.position();
- final int optionType = uint8(mPacket.get(position));
- final int optionLength = uint8(mPacket.get(position + 1)) * 8;
+ final int optionType = getUint8(mPacket, position);
+ final int optionLength = getUint8(mPacket, position + 1) * 8;
long lifetime;
switch (optionType) {
case ICMP6_PREFIX_OPTION_TYPE:
@@ -511,7 +521,7 @@
break;
}
if (optionLength <= 0) {
- throw new IllegalArgumentException(String.format(
+ throw new InvalidRaException(String.format(
"Invalid option length opt=%d len=%d", optionType, optionLength));
}
mPacket.position(position + optionLength);
@@ -552,10 +562,10 @@
final long optionLifetime;
switch (lifetimeLength) {
case 2:
- optionLifetime = uint16(byteBuffer.getShort(offset));
+ optionLifetime = getUint16(byteBuffer, offset);
break;
case 4:
- optionLifetime = uint32(byteBuffer.getInt(offset));
+ optionLifetime = getUint32(byteBuffer, offset);
break;
default:
throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
@@ -925,8 +935,8 @@
// Execution will reach the end of the program if no filters match, which will pass the
// packet to the AP.
program = gen.generate();
- } catch (IllegalInstructionException e) {
- Log.e(TAG, "Program failed to generate: ", e);
+ } catch (IllegalInstructionException|IllegalStateException e) {
+ Log.e(TAG, "Failed to generate APF program.", e);
return;
}
mLastTimeInstalledProgram = curTime();
@@ -972,7 +982,8 @@
* if the current APF program should be updated.
* @return a ProcessRaResult enum describing what action was performed.
*/
- private synchronized ProcessRaResult processRa(byte[] packet, int length) {
+ @VisibleForTesting
+ synchronized ProcessRaResult processRa(byte[] packet, int length) {
if (VDBG) hexDump("Read packet = ", packet, length);
// Have we seen this RA before?
@@ -1011,7 +1022,7 @@
try {
ra = new Ra(packet, length);
} catch (Exception e) {
- Log.e(TAG, "Error parsing RA: " + e);
+ Log.e(TAG, "Error parsing RA", e);
return ProcessRaResult.PARSE_ERROR;
}
// Ignore 0 lifetime RAs.
@@ -1150,7 +1161,11 @@
return i & 0xffffffffL;
}
- private static long getUint16(ByteBuffer buffer, int position) {
+ private static int getUint8(ByteBuffer buffer, int position) {
+ return uint8(buffer.get(position));
+ }
+
+ private static int getUint16(ByteBuffer buffer, int position) {
return uint16(buffer.getShort(position));
}
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/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 9aa66fe..ef4bc02 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -7,6 +7,7 @@
import android.os.Build;
import android.os.SystemProperties;
import android.system.OsConstants;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
@@ -14,9 +15,8 @@
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
import java.nio.ShortBuffer;
-
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -725,7 +725,8 @@
* A subset of the optional parameters are parsed and are stored
* in object fields.
*/
- public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException
+ @VisibleForTesting
+ static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException
{
// bootp parameters
int transactionId;
@@ -894,8 +895,12 @@
+ 64 // skip server host name (64 chars)
+ 128); // skip boot file name (128 chars)
- int dhcpMagicCookie = packet.getInt();
+ // Ensure this is a DHCP packet with a magic cookie, and not BOOTP. http://b/31850211
+ if (packet.remaining() < 4) {
+ throw new ParseException(DhcpErrorEvent.DHCP_NO_COOKIE, "not a DHCP message");
+ }
+ int dhcpMagicCookie = packet.getInt();
if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) {
throw new ParseException(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE,
"Bad magic cookie 0x%08x, should be 0x%08x",
@@ -1090,7 +1095,13 @@
public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType)
throws ParseException {
ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
- return decodeFullPacket(buffer, pktType);
+ try {
+ return decodeFullPacket(buffer, pktType);
+ } catch (ParseException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ParseException(DhcpErrorEvent.PARSING_ERROR, e.getMessage());
+ }
}
/**
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index ee67d953..01d9304 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -33,6 +33,7 @@
import android.net.dhcp.DhcpClient;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
+import android.net.util.AvoidBadWifiTracker;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.RemoteException;
@@ -396,6 +397,7 @@
private final NetlinkTracker mNetlinkTracker;
private final WakeupMessage mProvisioningTimeoutAlarm;
private final WakeupMessage mDhcpActionTimeoutAlarm;
+ private final AvoidBadWifiTracker mAvoidBadWifiTracker;
private final LocalLog mLocalLog;
private final MessageHandlingLogger mMsgStateLogger;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
@@ -470,6 +472,8 @@
Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
}
+ mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler());
+
resetLinkProperties();
mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
@@ -670,7 +674,7 @@
// object that is a correct and complete assessment of what changed, taking
// account of the asymmetries described in the comments in this function.
// Then switch to using it everywhere (IpReachabilityMonitor, etc.).
- private static ProvisioningChange compareProvisioning(
+ private ProvisioningChange compareProvisioning(
LinkProperties oldLp, LinkProperties newLp) {
ProvisioningChange delta;
@@ -697,6 +701,25 @@
delta = ProvisioningChange.LOST_PROVISIONING;
}
+ final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+ final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+ final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+ // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+ // provisioning. Otherwise, when a hotspot that loses Internet
+ // access sends out a 0-lifetime RA to its clients, the clients
+ // will disconnect and then reconnect, avoiding the bad hotspot,
+ // instead of getting stuck on the bad hotspot. http://b/31827713 .
+ //
+ // This is incorrect because if the hotspot then regains Internet
+ // access with a different prefix, TCP connections on the
+ // deprecated addresses will remain stuck.
+ //
+ // Note that we can still be disconnected by IpReachabilityMonitor
+ // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+ // accompanying code in IpReachabilityMonitor) is unreachable.
+ final boolean ignoreIPv6ProvisioningLoss = !mAvoidBadWifiTracker.currentValue();
+
// Additionally:
//
// Partial configurations (e.g., only an IPv4 address with no DNS
@@ -709,8 +732,7 @@
// Because on such a network isProvisioned() will always return false,
// delta will never be LOST_PROVISIONING. So check for loss of
// provisioning here too.
- if ((oldLp.hasIPv4Address() && !newLp.hasIPv4Address()) ||
- (oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned())) {
+ if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
delta = ProvisioningChange.LOST_PROVISIONING;
}
@@ -719,8 +741,7 @@
// If the previous link properties had a global IPv6 address and an
// IPv6 default route then also consider the loss of that default route
// to be a loss of provisioning. See b/27962810.
- if (oldLp.hasGlobalIPv6Address() && oldLp.hasIPv6DefaultRoute() &&
- !newLp.hasIPv6DefaultRoute()) {
+ if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
delta = ProvisioningChange.LOST_PROVISIONING;
}
@@ -1165,7 +1186,8 @@
public void notifyLost(InetAddress ip, String logMsg) {
mCallback.onReachabilityLost(logMsg);
}
- });
+ },
+ mAvoidBadWifiTracker);
}
}
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index c6da3c3..a883e28 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -34,6 +34,7 @@
import android.net.netlink.StructNdaCacheInfo;
import android.net.netlink.StructNdMsg;
import android.net.netlink.StructNlMsgHdr;
+import android.net.util.AvoidBadWifiTracker;
import android.os.PowerManager;
import android.os.SystemClock;
import android.system.ErrnoException;
@@ -42,15 +43,16 @@
import android.util.Log;
import java.io.InterruptedIOException;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -149,6 +151,7 @@
private final String mInterfaceName;
private final int mInterfaceIndex;
private final Callback mCallback;
+ private final AvoidBadWifiTracker mAvoidBadWifiTracker;
private final NetlinkSocketObserver mNetlinkSocketObserver;
private final Thread mObserverThread;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
@@ -160,8 +163,7 @@
private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
@GuardedBy("mLock")
private int mIpWatchListVersion;
- @GuardedBy("mLock")
- private boolean mRunning;
+ private volatile boolean mRunning;
// Time in milliseconds of the last forced probe request.
private volatile long mLastProbeTimeMs;
@@ -219,8 +221,12 @@
return errno;
}
- public IpReachabilityMonitor(Context context, String ifName, Callback callback)
- throws IllegalArgumentException {
+ public IpReachabilityMonitor(Context context, String ifName, Callback callback) {
+ this(context, ifName, callback, null);
+ }
+
+ public IpReachabilityMonitor(Context context, String ifName, Callback callback,
+ AvoidBadWifiTracker tracker) throws IllegalArgumentException {
mInterfaceName = ifName;
int ifIndex = -1;
try {
@@ -232,13 +238,14 @@
mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG + "." + mInterfaceName);
mCallback = callback;
+ mAvoidBadWifiTracker = tracker;
mNetlinkSocketObserver = new NetlinkSocketObserver();
mObserverThread = new Thread(mNetlinkSocketObserver);
mObserverThread.start();
}
public void stop() {
- synchronized (mLock) { mRunning = false; }
+ mRunning = false;
clearLinkProperties();
mNetlinkSocketObserver.clearNetlinkSocket();
}
@@ -273,12 +280,6 @@
}
}
- private boolean stillRunning() {
- synchronized (mLock) {
- return mRunning;
- }
- }
-
private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
for (RouteInfo route : routes) {
if (!route.hasGateway() && route.matches(ip)) {
@@ -355,7 +356,11 @@
whatIfLp.removeRoute(route);
}
}
- whatIfLp.removeDnsServer(ip);
+
+ if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
+ // We should do this unconditionally, but alas we cannot: b/31827713.
+ whatIfLp.removeDnsServer(ip);
+ }
}
delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
@@ -373,13 +378,17 @@
logNudFailed(delta);
}
+ private boolean avoidingBadLinks() {
+ return (mAvoidBadWifiTracker != null) ? mAvoidBadWifiTracker.currentValue() : true;
+ }
+
public void probeAll() {
- Set<InetAddress> ipProbeList = new HashSet<InetAddress>();
+ final List<InetAddress> ipProbeList;
synchronized (mLock) {
- ipProbeList.addAll(mIpWatchList.keySet());
+ ipProbeList = new ArrayList<>(mIpWatchList.keySet());
}
- if (!ipProbeList.isEmpty() && stillRunning()) {
+ if (!ipProbeList.isEmpty() && mRunning) {
// Keep the CPU awake long enough to allow all ARP/ND
// probes a reasonable chance at success. See b/23197666.
//
@@ -390,7 +399,7 @@
}
for (InetAddress target : ipProbeList) {
- if (!stillRunning()) {
+ if (!mRunning) {
break;
}
final int returnValue = probeNeighbor(mInterfaceIndex, target);
@@ -435,21 +444,21 @@
@Override
public void run() {
if (VDBG) { Log.d(TAG, "Starting observing thread."); }
- synchronized (mLock) { mRunning = true; }
+ mRunning = true;
try {
setupNetlinkSocket();
} catch (ErrnoException | SocketException e) {
Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
- synchronized (mLock) { mRunning = false; }
+ mRunning = false;
}
- ByteBuffer byteBuffer;
- while (stillRunning()) {
+ while (mRunning) {
+ final ByteBuffer byteBuffer;
try {
byteBuffer = recvKernelReply();
} catch (ErrnoException e) {
- if (stillRunning()) { Log.w(TAG, "ErrnoException: ", e); }
+ if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
break;
}
final long whenMs = SystemClock.elapsedRealtime();
@@ -461,7 +470,7 @@
clearNetlinkSocket();
- synchronized (mLock) { mRunning = false; }
+ mRunning = false; // Not a no-op when ErrnoException happened.
if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
}
diff --git a/services/net/java/android/net/util/AvoidBadWifiTracker.java b/services/net/java/android/net/util/AvoidBadWifiTracker.java
new file mode 100644
index 0000000..c14e811
--- /dev/null
+++ b/services/net/java/android/net/util/AvoidBadWifiTracker.java
@@ -0,0 +1,139 @@
+/*
+ * 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.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+
+import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
+
+/**
+ * A class to encapsulate management of the "Smart Networking" capability of
+ * avoiding bad Wi-Fi when, for example upstream connectivity is lost or
+ * certain critical link failures occur.
+ *
+ * This enables the device to switch to another form of connectivity, like
+ * mobile, if it's available and working.
+ *
+ * The Runnable |cb|, if given, is called on the supplied Handler's thread
+ * whether the computed "avoid bad wifi" value changes.
+ *
+ * Disabling this reverts the device to a level of networking sophistication
+ * circa 2012-13 by disabling disparate code paths each of which contribute to
+ * maintaining continuous, working Internet connectivity.
+ *
+ * @hide
+ */
+public class AvoidBadWifiTracker {
+ private static String TAG = AvoidBadWifiTracker.class.getSimpleName();
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Runnable mReevaluateRunnable;
+ private final SettingObserver mSettingObserver;
+ private volatile boolean mAvoidBadWifi = true;
+
+ public AvoidBadWifiTracker(Context ctx, Handler handler) {
+ this(ctx, handler, null);
+ }
+
+ public AvoidBadWifiTracker(Context ctx, Handler handler, Runnable cb) {
+ mContext = ctx;
+ mHandler = handler;
+ mReevaluateRunnable = () -> { if (update() && cb != null) cb.run(); };
+ mSettingObserver = new SettingObserver();
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ reevaluate();
+ }
+ }, UserHandle.ALL, intentFilter, null, null);
+
+ update();
+ }
+
+ public boolean currentValue() {
+ return mAvoidBadWifi;
+ }
+
+ /**
+ * Whether the device or carrier configuration disables avoiding bad wifi by default.
+ */
+ public boolean configRestrictsAvoidBadWifi() {
+ return (mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0);
+ }
+
+ /**
+ * Whether we should display a notification when wifi becomes unvalidated.
+ */
+ public boolean shouldNotifyWifiUnvalidated() {
+ return configRestrictsAvoidBadWifi() && getSettingsValue() == null;
+ }
+
+ public String getSettingsValue() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ return Settings.Global.getString(resolver, NETWORK_AVOID_BAD_WIFI);
+ }
+
+ @VisibleForTesting
+ public void reevaluate() {
+ mHandler.post(mReevaluateRunnable);
+ }
+
+ public boolean update() {
+ final boolean settingAvoidBadWifi = "1".equals(getSettingsValue());
+ final boolean prev = mAvoidBadWifi;
+ mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
+ return mAvoidBadWifi != prev;
+ }
+
+ private class SettingObserver extends ContentObserver {
+ private final Uri mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI);
+
+ public SettingObserver() {
+ super(null);
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mUri, false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Slog.wtf(TAG, "Should never be reached.");
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (!mUri.equals(uri)) return;
+ reevaluate();
+ }
+ }
+}
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 1feb816..6558b6e 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -728,7 +728,8 @@
@Override
public void onPackageModified(String packageName) {
if (!mUserManager.isUserUnlockingOrUnlocked(getChangingUserId())) return;
- UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false);
+ UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false,
+ false /* enforceUserUnlockingOrUnlocked */);
synchronized (mLock) {
if (hadPrintService(userState, packageName)
@@ -743,7 +744,8 @@
@Override
public void onPackageRemoved(String packageName, int uid) {
if (!mUserManager.isUserUnlockingOrUnlocked(getChangingUserId())) return;
- UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false);
+ UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false,
+ false /* enforceUserUnlockingOrUnlocked */);
synchronized (mLock) {
if (hadPrintService(userState, packageName)) {
@@ -762,8 +764,8 @@
// A background user/profile's print jobs are running but there is
// no UI shown. Hence, if the packages of such a user change we need
// to handle it as the change may affect ongoing print jobs.
- UserState userState = getOrCreateUserStateLocked(getChangingUserId(),
- false);
+ UserState userState = getOrCreateUserStateLocked(getChangingUserId(), false,
+ false /* enforceUserUnlockingOrUnlocked */);
boolean stoppedSomePackages = false;
List<PrintServiceInfo> enabledServices = userState
@@ -799,7 +801,7 @@
synchronized (mLock) {
if (hasPrintService(packageName)) {
UserState userState = getOrCreateUserStateLocked(getChangingUserId(),
- false);
+ false, false /* enforceUserUnlockingOrUnlocked */);
userState.updateIfNeededLocked();
}
}
@@ -810,9 +812,14 @@
monitor.register(mContext, BackgroundThread.getHandler().getLooper(),
UserHandle.ALL, true);
}
-
private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority) {
- if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ return getOrCreateUserStateLocked(userId, lowPriority,
+ true /* enforceUserUnlockingOrUnlocked */);
+ }
+
+ private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority,
+ boolean enforceUserUnlockingOrUnlocked) {
+ if (enforceUserUnlockingOrUnlocked && !mUserManager.isUserUnlockingOrUnlocked(userId)) {
throw new IllegalStateException(
"User " + userId + " must be unlocked for printing to be available");
}
@@ -840,7 +847,8 @@
UserState userState;
synchronized (mLock) {
- userState = getOrCreateUserStateLocked(userId, true);
+ userState = getOrCreateUserStateLocked(userId, true,
+ false /*enforceUserUnlockingOrUnlocked */);
userState.updateIfNeededLocked();
}
// This is the first time we switch to this user after boot, so
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index f97e557..7c7c299 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -124,7 +124,12 @@
@GuardedBy("mActivityLock")
long mLastUserActivityTime;
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ private boolean mSafeBootRestrictionInitialState;
+ private int mPackageVerifierEnableInitialState;
+
+ private IntentReceiver mBroadcastReceiver = null;
+
+ private final class IntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!mDeviceInDemoMode) {
@@ -150,6 +155,9 @@
@Override
public void handleMessage(Message msg) {
+ if (!mDeviceInDemoMode) {
+ return;
+ }
switch (msg.what) {
case MSG_TURN_SCREEN_ON:
if (mInjector.isWakeLockHeld()) {
@@ -219,7 +227,7 @@
if (mDeviceDemoModeUri.equals(uri)) {
mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
if (mDeviceInDemoMode) {
- putDeviceInDemoMode();
+ startDemoMode();
} else {
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
if (mInjector.isWakeLockHeld()) {
@@ -238,6 +246,7 @@
}
}
});
+ stopDemoMode();
}
}
@@ -376,10 +385,20 @@
}
private void registerBroadcastReceiver() {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(ACTION_RESET_DEMO);
- getContext().registerReceiver(mBroadcastReceiver, filter);
+ if (mBroadcastReceiver == null) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(ACTION_RESET_DEMO);
+ mBroadcastReceiver = new IntentReceiver();
+ getContext().registerReceiver(mBroadcastReceiver, filter);
+ }
+ }
+
+ private void unregisterBroadcastReceiver() {
+ if (mBroadcastReceiver != null) {
+ getContext().unregisterReceiver(mBroadcastReceiver);
+ mBroadcastReceiver = null;
+ }
}
private String[] getCameraIdsWithFlash() {
@@ -407,9 +426,33 @@
}
}
- private void putDeviceInDemoMode() {
+ private void startDemoMode() {
+ mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
+ mInjector.initializeWakeLock();
+ if (mCameraIdsWithFlash == null) {
+ mCameraIdsWithFlash = getCameraIdsWithFlash();
+ }
+ registerBroadcastReceiver();
+
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+
+ mSafeBootRestrictionInitialState = mInjector.getUserManager().hasUserRestriction(
+ UserManager.DISALLOW_SAFE_BOOT, UserHandle.SYSTEM);
+ mPackageVerifierEnableInitialState = Settings.Global.getInt(mInjector.getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+ }
+
+ private void stopDemoMode() {
+ mPreloadAppsInstaller = null;
+ mCameraIdsWithFlash = null;
+ mInjector.destroyWakeLock();
+ unregisterBroadcastReceiver();
+
+ mInjector.getUserManager().setUserRestriction(UserManager.DISALLOW_SAFE_BOOT,
+ mSafeBootRestrictionInitialState, UserHandle.SYSTEM);
+ Settings.Global.putInt(mInjector.getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_ENABLE, mPackageVerifierEnableInitialState);
}
@Override
@@ -421,25 +464,21 @@
false);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
- publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
+ mInjector.publishLocalService(this, mLocalService);
}
@Override
public void onBootPhase(int bootPhase) {
switch (bootPhase) {
case PHASE_THIRD_PARTY_APPS_CAN_START:
- mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
- mInjector.initializeWakeLock();
- mCameraIdsWithFlash = getCameraIdsWithFlash();
SettingsObserver settingsObserver = new SettingsObserver(mHandler);
settingsObserver.register();
settingsObserver.refreshTimeoutConstants();
- registerBroadcastReceiver();
break;
case PHASE_BOOT_COMPLETED:
if (UserManager.isDeviceInDemoMode(getContext())) {
mDeviceInDemoMode = true;
- putDeviceInDemoMode();
+ startDemoMode();
}
break;
}
@@ -466,8 +505,8 @@
mInjector.getSystemUsersConfiguration(), userId);
mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
muteVolumeStreams();
- if (!mInjector.isWifiEnabled()) {
- mInjector.enableWifi();
+ if (!mInjector.getWifiManager().isWifiEnabled()) {
+ mInjector.getWifiManager().setWifiEnabled(true);
}
// Disable lock screen for demo users.
mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
@@ -526,6 +565,7 @@
private WifiManager mWifiManager;
private Configuration mSystemUserConfiguration;
private PendingIntent mResetDemoPendingIntent;
+ private PreloadAppsInstaller mPreloadAppsInstaller;
Injector(Context context) {
mContext = context;
@@ -535,7 +575,7 @@
return mContext;
}
- private WifiManager getWifiManager() {
+ WifiManager getWifiManager() {
if (mWifiManager == null) {
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
}
@@ -609,7 +649,10 @@
}
PreloadAppsInstaller getPreloadAppsInstaller() {
- return new PreloadAppsInstaller(getContext());
+ if (mPreloadAppsInstaller == null) {
+ mPreloadAppsInstaller = new PreloadAppsInstaller(getContext());
+ }
+ return mPreloadAppsInstaller;
}
void systemPropertiesSet(String key, String value) {
@@ -628,8 +671,14 @@
}
void initializeWakeLock() {
- mWakeLock = getPowerManager().newWakeLock(
- PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ if (mWakeLock == null) {
+ mWakeLock = getPowerManager().newWakeLock(
+ PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ }
+ }
+
+ void destroyWakeLock() {
+ mWakeLock = null;
}
boolean isWakeLockHeld() {
@@ -644,14 +693,6 @@
mWakeLock.release();
}
- boolean isWifiEnabled() {
- return getWifiManager().isWifiEnabled();
- }
-
- void enableWifi() {
- getWifiManager().setWifiEnabled(true);
- }
-
void logSessionDuration(int duration) {
MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, duration);
}
@@ -696,5 +737,10 @@
File getDataPreloadsDirectory() {
return Environment.getDataPreloadsDirectory();
}
+
+ void publishLocalService(RetailDemoModeService service,
+ RetailDemoModeServiceInternal localService) {
+ service.publishLocalService(RetailDemoModeServiceInternal.class, localService);
+ }
}
}
diff --git a/services/tests/runtests.py b/services/tests/runtests.py
new file mode 100755
index 0000000..35fec90f
--- /dev/null
+++ b/services/tests/runtests.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# 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.
+
+import os
+import subprocess
+import sys
+
+INSTRUMENTED_PACKAGE_RUNNER = ('com.android.frameworks.servicestests/'
+ 'android.support.test.runner.AndroidJUnitRunner')
+
+PACKAGE_WHITELIST = (
+ 'android.net',
+ 'com.android.server.connectivity',
+)
+
+COLOR_RED = '\033[0;31m'
+COLOR_NONE ='\033[0m'
+
+def run(shell_command, echo=True):
+ if echo:
+ print '%s + %s%s' % (
+ COLOR_RED,
+ echo if isinstance(echo, str) else shell_command,
+ COLOR_NONE)
+ return subprocess.check_call(shell_command, shell=True)
+
+
+def main():
+ build_top = os.environ.get('ANDROID_BUILD_TOP', None)
+ out_dir = os.environ.get('OUT', None)
+ if build_top is None or out_dir is None:
+ print 'You need to source and lunch before you can use this script'
+ return 1
+
+ print 'Building tests...'
+ run('make -j32 -C %s -f build/core/main.mk '
+ 'MODULES-IN-frameworks-base-services-tests-servicestests' % build_top,
+ echo='mmma -j32 %s/frameworks/base/services/tests/servicestests' %
+ build_top)
+
+ print 'Installing tests...'
+ run('adb root')
+ run('adb wait-for-device')
+ apk_path = (
+ '%s/data/app/FrameworksServicesTests/FrameworksServicesTests.apk' %
+ out_dir)
+ run('adb install -r -g "%s"' % apk_path)
+
+ print 'Running tests...'
+ if len(sys.argv) != 1:
+ run('adb shell am instrument -w "%s" %s' %
+ (INSTRUMENTED_PACKAGE_RUNNER, ' '.join(sys.argv[1:])))
+ return 0
+
+ # It would be nice if the activity manager accepted a list of packages, but
+ # in lieu of that...
+ for package in PACKAGE_WHITELIST:
+ run('adb shell am instrument -w -e package %s %s' %
+ (package, INSTRUMENTED_PACKAGE_RUNNER))
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index b76392c..3f5b96e 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -53,6 +53,8 @@
LOCAL_JACK_FLAGS := --multi-dex native
endif # EMMA_INSTRUMENT_STATIC
+LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
+
include $(BUILD_PACKAGE)
#########################################################################
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index b8ace28..3548f28 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -43,6 +43,8 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<application>
<uses-library android:name="android.test.runner" />
@@ -155,6 +157,9 @@
</intent-filter>
</activity-alias>
+ <activity android:name="com.android.server.am.TaskStackChangedListenerTest$ActivityA" />
+ <activity android:name="com.android.server.am.TaskStackChangedListenerTest$ActivityB" />
+
</application>
<instrumentation
diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java
index f7c61d1..37807b2 100644
--- a/services/tests/servicestests/src/android/net/apf/ApfTest.java
+++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java
@@ -16,10 +16,6 @@
package android.net.apf;
-import static android.system.OsConstants.*;
-
-import com.android.frameworks.servicestests.R;
-
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
@@ -37,6 +33,10 @@
import android.system.Os;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
+import static android.system.OsConstants.*;
+
+import com.android.frameworks.servicestests.R;
+import com.android.internal.util.HexDump;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -54,6 +54,7 @@
import java.net.NetworkInterface;
import java.nio.ByteBuffer;
import java.util.List;
+import java.util.Random;
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -1146,6 +1147,39 @@
buffer.position(original);
}
+ public void testRaParsing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpManagerCallback cb = new MockIpManagerCallback();
+ TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, mLog);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.new Ra(packet, packet.length);
+ } catch (ApfFilter.InvalidRaException e) {
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ public void testRaProcessing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpManagerCallback cb = new MockIpManagerCallback();
+ TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, mLog);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.processRa(packet, packet.length);
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
/**
* Call the APF interpreter the run {@code program} on {@code packet} pretending the
* filter was installed {@code filter_age} seconds ago.
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
index f8eaf7d..bc8baa1 100644
--- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
@@ -16,24 +16,22 @@
package android.net.dhcp;
-import android.net.NetworkUtils;
import android.net.DhcpResults;
import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.metrics.DhcpErrorEvent;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.util.HexDump;
-
import java.net.Inet4Address;
import java.nio.ByteBuffer;
import java.util.ArrayList;
-
-import junit.framework.TestCase;
-import libcore.util.HexEncoding;
import java.util.Arrays;
+import java.util.Random;
+import junit.framework.TestCase;
import static android.net.dhcp.DhcpPacket.*;
-
public class DhcpPacketTest extends TestCase {
private static Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
@@ -285,7 +283,7 @@
// TODO: Turn all of these into golden files. This will probably require modifying
// Android.mk appropriately, making this into an AndroidTestCase, and adding code to read
// the golden files from the test APK's assets via mContext.getAssets().
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// IP header.
"451001480000000080118849c0a89003c0a89ff7" +
// UDP header.
@@ -304,8 +302,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options
"638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
- "3a0400000e103b040000189cff00000000000000000000"
- ).toCharArray(), false));
+ "3a0400000e103b040000189cff00000000000000000000"));
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null.
@@ -316,7 +313,7 @@
@SmallTest
public void testOffer2() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// IP header.
"450001518d0600004011144dc0a82b01c0a82bf7" +
// UDP header.
@@ -335,8 +332,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options
"638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
- "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"
- ).toCharArray(), false));
+ "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
assertEquals(337, packet.limit());
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
@@ -347,6 +343,185 @@
assertTrue(dhcpResults.hasMeteredHint());
}
+ @SmallTest
+ public void testBadIpPacket() throws Exception {
+ final byte[] packet = HexDump.hexStringToByteArray(
+ // IP header.
+ "450001518d0600004011144dc0a82b01c0a82bf7");
+
+ try {
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
+ } catch (DhcpPacket.ParseException expected) {
+ assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
+ return;
+ }
+ fail("Dhcp packet parsing should have failed");
+ }
+
+ @SmallTest
+ public void testBadDhcpPacket() throws Exception {
+ final byte[] packet = HexDump.hexStringToByteArray(
+ // IP header.
+ "450001518d0600004011144dc0a82b01c0a82bf7" +
+ // UDP header.
+ "00430044013d9ac7" +
+ // BOOTP header.
+ "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000");
+
+ try {
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
+ } catch (DhcpPacket.ParseException expected) {
+ assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
+ return;
+ }
+ fail("Dhcp packet parsing should have failed");
+ }
+
+ @SmallTest
+ public void testBadTruncatedOffer() throws Exception {
+ final byte[] packet = HexDump.hexStringToByteArray(
+ // IP header.
+ "450001518d0600004011144dc0a82b01c0a82bf7" +
+ // UDP header.
+ "00430044013d9ac7" +
+ // BOOTP header.
+ "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
+ // MAC address.
+ "30766ff2a90c00000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File, missing one byte
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "00000000000000000000000000000000000000000000000000000000000000");
+
+ try {
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
+ } catch (DhcpPacket.ParseException expected) {
+ assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
+ return;
+ }
+ fail("Dhcp packet parsing should have failed");
+ }
+
+ @SmallTest
+ public void testBadOfferWithoutACookie() throws Exception {
+ final byte[] packet = HexDump.hexStringToByteArray(
+ // IP header.
+ "450001518d0600004011144dc0a82b01c0a82bf7" +
+ // UDP header.
+ "00430044013d9ac7" +
+ // BOOTP header.
+ "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
+ // MAC address.
+ "30766ff2a90c00000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ // No options
+ );
+
+ try {
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
+ } catch (DhcpPacket.ParseException expected) {
+ assertDhcpErrorCodes(DhcpErrorEvent.DHCP_NO_COOKIE, expected.errorCode);
+ return;
+ }
+ fail("Dhcp packet parsing should have failed");
+ }
+
+ @SmallTest
+ public void testOfferWithBadCookie() throws Exception {
+ final byte[] packet = HexDump.hexStringToByteArray(
+ // IP header.
+ "450001518d0600004011144dc0a82b01c0a82bf7" +
+ // UDP header.
+ "00430044013d9ac7" +
+ // BOOTP header.
+ "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
+ // MAC address.
+ "30766ff2a90c00000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // Bad cookie
+ "DEADBEEF3501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
+ "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
+
+ try {
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
+ } catch (DhcpPacket.ParseException expected) {
+ assertDhcpErrorCodes(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE, expected.errorCode);
+ return;
+ }
+ fail("Dhcp packet parsing should have failed");
+ }
+
+ private void assertDhcpErrorCodes(int expected, int got) {
+ assertEquals(Integer.toHexString(expected), Integer.toHexString(got));
+ }
+
+ public void testTruncatedOfferPackets() throws Exception {
+ final byte[] packet = HexDump.hexStringToByteArray(
+ // IP header.
+ "450001518d0600004011144dc0a82b01c0a82bf7" +
+ // UDP header.
+ "00430044013d9ac7" +
+ // BOOTP header.
+ "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
+ // MAC address.
+ "30766ff2a90c00000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // Options
+ "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
+ "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
+
+ for (int len = 0; len < packet.length; len++) {
+ try {
+ DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3);
+ } catch (ParseException e) {
+ if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
+ fail(String.format("bad truncated packet of length %d", len));
+ }
+ }
+ }
+ }
+
+ public void testRandomPackets() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ for (int i = 0; i < 10000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
+ } catch (ParseException e) {
+ if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
+ fail("bad packet: " + HexDump.toHexString(packet));
+ }
+ }
+ }
+ }
+
private byte[] mtuBytes(int mtu) {
// 0x1a02: option 26, length 2. 0xff: no more options.
if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) {
@@ -354,7 +529,7 @@
String.format("Invalid MTU %d, must be 16-bit unsigned", mtu));
}
String hexString = String.format("1a02%04xff", mtu);
- return HexEncoding.decode(hexString.toCharArray(), false);
+ return HexDump.hexStringToByteArray(hexString);
}
private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception {
@@ -372,7 +547,7 @@
@SmallTest
public void testMtu() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// IP header.
"451001480000000080118849c0a89003c0a89ff7" +
// UDP header.
@@ -391,8 +566,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options
"638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
- "3a0400000e103b040000189cff00000000"
- ).toCharArray(), false));
+ "3a0400000e103b040000189cff00000000"));
checkMtu(packet, 0, null);
checkMtu(packet, 0, mtuBytes(1501));
@@ -409,7 +583,7 @@
@SmallTest
public void testBadHwaddrLength() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// IP header.
"450001518d0600004011144dc0a82b01c0a82bf7" +
// UDP header.
@@ -428,8 +602,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options
"638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
- "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"
- ).toCharArray(), false));
+ "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
String expectedClientMac = "30766FF2A90C";
final int hwAddrLenOffset = 20 + 8 + 2;
@@ -486,7 +659,7 @@
// store any information in the overloaded fields).
//
// For now, we just check that it parses correctly.
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// Ethernet header.
"b4cef6000000e80462236e300800" +
// IP header.
@@ -507,8 +680,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options
"638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" +
- "0000000000000000000000000000000000000000000000ff000000"
- ).toCharArray(), false));
+ "0000000000000000000000000000000000000000000000ff000000"));
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
assertTrue(offerPacket instanceof DhcpOfferPacket);
@@ -519,7 +691,7 @@
@SmallTest
public void testBug2111() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// IP header.
"4500014c00000000ff119beac3eaf3880a3f5d04" +
// UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
@@ -538,8 +710,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options.
"638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" +
- "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"
- ).toCharArray(), false));
+ "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"));
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
assertTrue(offerPacket instanceof DhcpOfferPacket);
@@ -550,7 +721,7 @@
@SmallTest
public void testBug2136() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// Ethernet header.
"bcf5ac000000d0c7890000000800" +
// IP header.
@@ -571,8 +742,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options.
"6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" +
- "0f0b6c616e63732e61632e756b000000000000000000ff00000000"
- ).toCharArray(), false));
+ "0f0b6c616e63732e61632e756b000000000000000000ff00000000"));
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
assertTrue(offerPacket instanceof DhcpOfferPacket);
@@ -584,7 +754,7 @@
@SmallTest
public void testUdpServerAnySourcePort() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// Ethernet header.
"9cd917000000001c2e0000000800" +
// IP header.
@@ -606,8 +776,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options.
"6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
- "d18180060f0777766d2e6564751c040a0fffffff000000"
- ).toCharArray(), false));
+ "d18180060f0777766d2e6564751c040a0fffffff000000"));
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
assertTrue(offerPacket instanceof DhcpOfferPacket);
@@ -620,7 +789,7 @@
@SmallTest
public void testUdpInvalidDstPort() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// Ethernet header.
"9cd917000000001c2e0000000800" +
// IP header.
@@ -642,8 +811,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options.
"6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
- "d18180060f0777766d2e6564751c040a0fffffff000000"
- ).toCharArray(), false));
+ "d18180060f0777766d2e6564751c040a0fffffff000000"));
try {
DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
@@ -653,7 +821,7 @@
@SmallTest
public void testMultipleRouters() throws Exception {
- final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
// Ethernet header.
"fc3d93000000" + "081735000000" + "0800" +
// IP header.
@@ -674,8 +842,7 @@
"0000000000000000000000000000000000000000000000000000000000000000" +
// Options.
"638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
- "0308c0a8bd01ffffff0006080808080808080404ff000000000000"
- ).toCharArray(), false));
+ "0308c0a8bd01ffffff0006080808080808080404ff000000000000"));
DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
assertTrue(offerPacket instanceof DhcpOfferPacket);
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 340be62..826aeeb 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -53,6 +53,7 @@
import android.net.NetworkRequest;
import android.net.RouteInfo;
import android.net.metrics.IpConnectivityLog;
+import android.net.util.AvoidBadWifiTracker;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -67,6 +68,7 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.test.AndroidTestCase;
+import android.test.FlakyTest;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -214,8 +216,20 @@
mService.waitForIdle();
assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
}
+ }
+
+ @FlakyTest(tolerance = 3)
+ public void testNotWaitingForIdleCausesRaceConditions() {
+ // Bring up a network that we can use to send messages to ConnectivityService.
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ waitFor(cv);
+ Network n = mWiFiNetworkAgent.getNetwork();
+ assertNotNull(n);
// Ensure that not calling waitForIdle causes a race condition.
+ final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng.
for (int i = 0; i < attempts; i++) {
mWiFiNetworkAgent.setSignalStrength(i);
if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) {
@@ -235,6 +249,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;
@@ -290,6 +305,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.
@@ -374,11 +394,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() {
@@ -390,6 +405,10 @@
return new Network(mNetworkAgent.netId);
}
+ public ConditionVariable getPreventReconnectReceived() {
+ return mPreventReconnectReceived;
+ }
+
public ConditionVariable getDisconnectedCV() {
return mDisconnected;
}
@@ -596,13 +615,27 @@
@Override
protected CaptivePortalProbeResult isCaptivePortal() {
- return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl);
+ if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
+ return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
+ }
+ }
+
+ private class WrappedAvoidBadWifiTracker extends AvoidBadWifiTracker {
+ public boolean configRestrictsAvoidBadWifi;
+
+ public WrappedAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
+ super(c, h, r);
+ }
+
+ @Override
+ public boolean configRestrictsAvoidBadWifi() {
+ return configRestrictsAvoidBadWifi;
}
}
private class WrappedConnectivityService extends ConnectivityService {
+ public WrappedAvoidBadWifiTracker wrappedAvoidBadWifiTracker;
private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
- public boolean configRestrictsAvoidBadWifi;
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
@@ -653,14 +686,20 @@
}
@Override
- public WakeupMessage makeWakeupMessage(
- Context context, Handler handler, String cmdName, int cmd, Object obj) {
- return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
+ public AvoidBadWifiTracker createAvoidBadWifiTracker(
+ Context c, Handler h, Runnable r) {
+ final WrappedAvoidBadWifiTracker tracker = new WrappedAvoidBadWifiTracker(c, h, r);
+ return tracker;
+ }
+
+ public WrappedAvoidBadWifiTracker getAvoidBadWifiTracker() {
+ return (WrappedAvoidBadWifiTracker) mAvoidBadWifiTracker;
}
@Override
- public boolean configRestrictsAvoidBadWifi() {
- return configRestrictsAvoidBadWifi;
+ public WakeupMessage makeWakeupMessage(
+ Context context, Handler handler, String cmdName, int cmd, Object obj) {
+ return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
}
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
@@ -723,6 +762,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 {
@@ -1690,6 +1732,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;
@@ -1830,6 +1913,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);
@@ -2049,46 +2137,48 @@
@SmallTest
public void testAvoidBadWifiSetting() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
+ final WrappedAvoidBadWifiTracker tracker = mService.getAvoidBadWifiTracker();
final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
- mService.configRestrictsAvoidBadWifi = false;
+ tracker.configRestrictsAvoidBadWifi = false;
String[] values = new String[] {null, "0", "1"};
for (int i = 0; i < values.length; i++) {
Settings.Global.putInt(cr, settingName, 1);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
mService.waitForIdle();
String msg = String.format("config=false, setting=%s", values[i]);
assertTrue(msg, mService.avoidBadWifi());
- assertFalse(msg, mService.shouldNotifyWifiUnvalidated());
+ assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
}
- mService.configRestrictsAvoidBadWifi = true;
+ tracker.configRestrictsAvoidBadWifi = true;
Settings.Global.putInt(cr, settingName, 0);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
mService.waitForIdle();
assertFalse(mService.avoidBadWifi());
- assertFalse(mService.shouldNotifyWifiUnvalidated());
+ assertFalse(tracker.shouldNotifyWifiUnvalidated());
Settings.Global.putInt(cr, settingName, 1);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
mService.waitForIdle();
assertTrue(mService.avoidBadWifi());
- assertFalse(mService.shouldNotifyWifiUnvalidated());
+ assertFalse(tracker.shouldNotifyWifiUnvalidated());
Settings.Global.putString(cr, settingName, null);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
mService.waitForIdle();
assertFalse(mService.avoidBadWifi());
- assertTrue(mService.shouldNotifyWifiUnvalidated());
+ assertTrue(tracker.shouldNotifyWifiUnvalidated());
}
@SmallTest
public void testAvoidBadWifi() throws Exception {
- ContentResolver cr = mServiceContext.getContentResolver();
+ final ContentResolver cr = mServiceContext.getContentResolver();
+ final WrappedAvoidBadWifiTracker tracker = mService.getAvoidBadWifiTracker();
// Pretend we're on a carrier that restricts switching away from bad wifi.
- mService.configRestrictsAvoidBadWifi = true;
+ tracker.configRestrictsAvoidBadWifi = true;
// File a request for cell to ensure it doesn't go down.
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -2107,7 +2197,7 @@
mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
// Bring up validated cell.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -2138,14 +2228,14 @@
// Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect
// that we switch back to cell.
- mService.configRestrictsAvoidBadWifi = false;
- mService.updateNetworkAvoidBadWifi();
+ tracker.configRestrictsAvoidBadWifi = false;
+ tracker.reevaluate();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Switch back to a restrictive carrier.
- mService.configRestrictsAvoidBadWifi = true;
- mService.updateNetworkAvoidBadWifi();
+ tracker.configRestrictsAvoidBadWifi = true;
+ tracker.reevaluate();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
@@ -2173,7 +2263,7 @@
// Simulate the user selecting "switch" and checking the don't ask again checkbox.
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
// We now switch to cell.
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
@@ -2186,11 +2276,11 @@
// Simulate the user turning the cellular fallback setting off and then on.
// We switch to wifi and then to cell.
Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
- mService.updateNetworkAvoidBadWifi();
+ tracker.reevaluate();
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e72a8dc..9c241d7 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.accounts;
+import static android.database.sqlite.SQLiteDatabase.deleteDatabase;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -24,7 +26,7 @@
import android.accounts.AccountManagerInternal;
import android.accounts.AuthenticatorDescription;
import android.app.AppOpsManager;
-import android.app.Notification;
+import android.app.INotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -38,16 +40,14 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
-import com.android.server.LocalServices;
-
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -55,29 +55,38 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class AccountManagerServiceTest extends AndroidTestCase {
private static final String TAG = AccountManagerServiceTest.class.getSimpleName();
- static final String PREN_DB = "pren.db";
- static final String DE_DB = "de.db";
- static final String CE_DB = "ce.db";
+ private static final String PREN_DB = "pren.db";
+ private static final String DE_DB = "de.db";
+ private static final String CE_DB = "ce.db";
private AccountManagerService mAms;
+ private TestInjector mTestInjector;
@Override
protected void setUp() throws Exception {
Context realTestContext = getContext();
- Context mockContext = new MyMockContext(realTestContext);
+ MyMockContext mockContext = new MyMockContext(realTestContext);
setContext(mockContext);
- mAms = createAccountManagerService(mockContext, realTestContext);
+ mTestInjector = new TestInjector(realTestContext, mockContext);
+ mAms = new AccountManagerService(mTestInjector);
}
@Override
protected void tearDown() throws Exception {
- SQLiteDatabase.deleteDatabase(new File(mAms.getCeDatabaseName(UserHandle.USER_SYSTEM)));
- SQLiteDatabase.deleteDatabase(new File(mAms.getDeDatabaseName(UserHandle.USER_SYSTEM)));
- SQLiteDatabase.deleteDatabase(new File(mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM)));
- LocalServices.removeServiceForTest(AccountManagerInternal.class);
+ // Let async logging tasks finish, otherwise they may crash due to db being removed
+ CountDownLatch cdl = new CountDownLatch(1);
+ mAms.mHandler.post(() -> {
+ deleteDatabase(new File(mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM)));
+ deleteDatabase(new File(mTestInjector.getDeDatabaseName(UserHandle.USER_SYSTEM)));
+ deleteDatabase(new File(mTestInjector.getPreNDatabaseName(UserHandle.USER_SYSTEM)));
+ cdl.countDown();
+ });
+ cdl.await(1, TimeUnit.SECONDS);
super.tearDown();
}
@@ -230,7 +239,7 @@
Context originalContext = ((MyMockContext)getContext()).mTestContext;
// create a separate instance of AMS. It initially assumes that user0 is locked
- AccountManagerService ams2 = createAccountManagerService(getContext(), originalContext);
+ AccountManagerService ams2 = new AccountManagerService(mTestInjector);
// Verify that account can be removed when user is locked
ams2.removeAccountInternal(a1);
@@ -239,7 +248,7 @@
assertEquals("Only a2 should be returned", a2, accounts[0]);
// Verify that CE db file is unchanged and still has 2 accounts
- String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM);
+ String ceDatabaseName = mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM);
int accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName);
assertEquals("CE database should still have 2 accounts", 2, accountsNumber);
@@ -254,7 +263,7 @@
@SmallTest
public void testPreNDatabaseMigration() throws Exception {
- String preNDatabaseName = mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM);
+ String preNDatabaseName = mTestInjector.getPreNDatabaseName(UserHandle.USER_SYSTEM);
Context originalContext = ((MyMockContext) getContext()).mTestContext;
PreNTestDatabaseHelper.createV4Database(originalContext, preNDatabaseName);
// Assert that database was created with 1 account
@@ -275,8 +284,8 @@
new File(preNDatabaseName).exists());
// Verify that ce/de files are present
- String deDatabaseName = mAms.getDeDatabaseName(UserHandle.USER_SYSTEM);
- String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM);
+ String deDatabaseName = mTestInjector.getDeDatabaseName(UserHandle.USER_SYSTEM);
+ String ceDatabaseName = mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM);
assertTrue("DE database file should be created at " + deDatabaseName,
new File(deDatabaseName).exists());
assertTrue("CE database file should be created at " + ceDatabaseName,
@@ -291,13 +300,6 @@
}
}
- private AccountManagerService createAccountManagerService(Context mockContext,
- Context realContext) {
- LocalServices.removeServiceForTest(AccountManagerInternal.class);
- return new MyAccountManagerService(mockContext,
- new MyMockPackageManager(), new MockAccountAuthenticatorCache(), realContext);
- }
-
private void unlockSystemUser() {
mAms.onUserUnlocked(newIntentForUser(UserHandle.USER_SYSTEM));
}
@@ -366,6 +368,8 @@
this.mAppOpsManager = mock(AppOpsManager.class);
this.mUserManager = mock(UserManager.class);
this.mPackageManager = mock(PackageManager.class);
+ when(mPackageManager.checkSignatures(anyInt(), anyInt()))
+ .thenReturn(PackageManager.SIGNATURE_MATCH);
final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0);
when(mUserManager.getUserInfo(eq(ui.id))).thenReturn(ui);
}
@@ -381,6 +385,11 @@
}
@Override
+ public String getPackageName() {
+ return mTestContext.getPackageName();
+ }
+
+ @Override
public Object getSystemService(String name) {
if (Context.APP_OPS_SERVICE.equals(name)) {
return mAppOpsManager;
@@ -427,47 +436,45 @@
}
}
- static class MyMockPackageManager extends MockPackageManager {
- @Override
- public int checkSignatures(final int uid1, final int uid2) {
- return PackageManager.SIGNATURE_MATCH;
+ static class TestInjector extends AccountManagerService.Injector {
+ private Context mRealContext;
+ TestInjector(Context realContext, MyMockContext mockContext) {
+ super(mockContext);
+ mRealContext = realContext;
}
@Override
- public void addOnPermissionsChangeListener(
- OnPermissionsChangedListener listener) {
- }
- }
-
- static class MyAccountManagerService extends AccountManagerService {
- private Context mRealTestContext;
- MyAccountManagerService(Context context, PackageManager packageManager,
- IAccountAuthenticatorCache authenticatorCache, Context realTestContext) {
- super(context, packageManager, authenticatorCache);
- this.mRealTestContext = realTestContext;
+ Looper getMessageHandlerLooper() {
+ return Looper.getMainLooper();
}
@Override
- protected void installNotification(final int notificationId, final Notification n, UserHandle user) {
+ void addLocalService(AccountManagerInternal service) {
}
@Override
- protected void cancelNotification(final int id, UserHandle user) {
+ IAccountAuthenticatorCache getAccountAuthenticatorCache() {
+ return new MockAccountAuthenticatorCache();
}
@Override
protected String getCeDatabaseName(int userId) {
- return new File(mRealTestContext.getCacheDir(), CE_DB).getPath();
+ return new File(mRealContext.getCacheDir(), CE_DB).getPath();
}
@Override
protected String getDeDatabaseName(int userId) {
- return new File(mRealTestContext.getCacheDir(), DE_DB).getPath();
+ return new File(mRealContext.getCacheDir(), DE_DB).getPath();
}
@Override
String getPreNDatabaseName(int userId) {
- return new File(mRealTestContext.getCacheDir(), PREN_DB).getPath();
+ return new File(mRealContext.getCacheDir(), PREN_DB).getPath();
+ }
+
+ @Override
+ INotificationManager getNotificationManager() {
+ return mock(INotificationManager.class);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
new file mode 100644
index 0000000..5b565a7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -0,0 +1,341 @@
+
+/*
+ * 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.accounts;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.database.Cursor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for {@link AccountsDb}.
+ * <p>Run with:<pre>
+ * m FrameworksServicesTests &&
+ * adb install \
+ * -r out/target/product/marlin/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ * adb shell am instrument -e class com.android.server.accounts.AccountsDbTest \
+ * -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AccountsDbTest {
+ private static final String PREN_DB = "pren.db";
+ private static final String DE_DB = "de.db";
+ private static final String CE_DB = "ce.db";
+
+ private AccountsDb mAccountsDb;
+ private File preNDb;
+ private File deDb;
+ private File ceDb;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ preNDb = new File(context.getCacheDir(), PREN_DB);
+ ceDb = new File(context.getCacheDir(), CE_DB);
+ deDb = new File(context.getCacheDir(), DE_DB);
+ deleteDbFiles();
+ mAccountsDb = AccountsDb.create(context, 0, preNDb, deDb);
+ }
+
+ @After
+ public void tearDown() {
+ deleteDbFiles();
+ }
+
+ private void deleteDbFiles() {
+ AccountsDb.deleteDbFileWarnIfFailed(preNDb);
+ AccountsDb.deleteDbFileWarnIfFailed(ceDb);
+ AccountsDb.deleteDbFileWarnIfFailed(deDb);
+ }
+
+ @Test
+ public void testCeNotAvailableInitially() {
+ Account account = new Account("name", "example.com");
+ long id = mAccountsDb.insertCeAccount(account, "");
+ assertEquals("Insert into CE should fail until CE database is attached", -1, id);
+ }
+
+ @Test
+ public void testDeAccountInsertFindDelete() {
+ Account account = new Account("name", "example.com");
+ long accId = 1;
+ mAccountsDb.insertDeAccount(account, accId);
+ long actualId = mAccountsDb.findDeAccountId(account);
+ assertEquals(accId, actualId);
+ // Delete and verify that account no longer exists
+ mAccountsDb.deleteDeAccount(accId);
+ actualId = mAccountsDb.findDeAccountId(account);
+ assertEquals(-1, actualId);
+ }
+
+ @Test
+ public void testCeAccountInsertFindDelete() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ long actualId = mAccountsDb.findCeAccountId(account);
+ assertEquals(accId, actualId);
+ // Delete and verify that account no longer exists
+ mAccountsDb.deleteCeAccount(accId);
+ actualId = mAccountsDb.findCeAccountId(account);
+ assertEquals(-1, actualId);
+ }
+
+ @Test
+ public void testAuthTokenInsertFindDelete() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ mAccountsDb.insertDeAccount(account, accId);
+ long authTokenId = mAccountsDb.insertAuthToken(accId, "type", "token");
+ Map<String, String> authTokensByAccount = mAccountsDb.findAuthTokensByAccount(account);
+ assertEquals(1, authTokensByAccount.size());
+ try (Cursor cursor = mAccountsDb.findAuthtokenForAllAccounts(account.type, "token")) {
+ assertTrue(cursor.moveToNext());
+ }
+ try (Cursor cursor = mAccountsDb.findAuthtokenForAllAccounts(account.type, "nosuchtoken")) {
+ assertFalse(cursor.moveToNext());
+ }
+ mAccountsDb.deleteAuthToken(String.valueOf(authTokenId));
+ // Verify that token no longer exists
+ authTokensByAccount = mAccountsDb.findAuthTokensByAccount(account);
+ assertEquals(0, authTokensByAccount.size());
+ }
+
+ @Test
+ public void testAuthTokenDeletes() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ // 1st account
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ mAccountsDb.insertDeAccount(account, accId);
+ mAccountsDb.insertAuthToken(accId, "type", "token");
+ mAccountsDb.insertAuthToken(accId, "type2", "token2");
+ // 2nd account
+ Account account2 = new Account("name", "example2.com");
+ long accId2 = mAccountsDb.insertCeAccount(account2, "password");
+ mAccountsDb.insertDeAccount(account2, accId);
+ mAccountsDb.insertAuthToken(accId2, "type", "token");
+
+ mAccountsDb.deleteAuthTokensByAccountId(accId2);
+ Map<String, String> authTokensByAccount = mAccountsDb.findAuthTokensByAccount(account2);
+ assertEquals(0, authTokensByAccount.size());
+ // Authtokens from account 1 are still there
+ authTokensByAccount = mAccountsDb.findAuthTokensByAccount(account);
+ assertEquals(2, authTokensByAccount.size());
+
+ // Delete authtokens from account 1 and verify
+ mAccountsDb.deleteAuthtokensByAccountIdAndType(accId, "type");
+ authTokensByAccount = mAccountsDb.findAuthTokensByAccount(account);
+ assertEquals(1, authTokensByAccount.size());
+ mAccountsDb.deleteAuthtokensByAccountIdAndType(accId, "type2");
+ authTokensByAccount = mAccountsDb.findAuthTokensByAccount(account);
+ assertEquals(0, authTokensByAccount.size());
+ }
+
+ @Test
+ public void testExtrasInsertFindDelete() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ mAccountsDb.insertDeAccount(account, accId);
+ String extraKey = "extra_key";
+ String extraValue = "extra_value";
+ long extraId = mAccountsDb.insertExtra(accId, extraKey, extraValue);
+ // Test find methods
+ long actualExtraId = mAccountsDb.findExtrasIdByAccountId(accId, extraKey);
+ assertEquals(extraId, actualExtraId);
+ Map<String, String> extras = mAccountsDb.findUserExtrasForAccount(account);
+ assertEquals(1, extras.size());
+ assertEquals(extraValue, extras.get(extraKey));
+ // Test update
+ String newExtraValue = "extra_value2";
+ mAccountsDb.updateExtra(extraId, newExtraValue);
+ String newValue = mAccountsDb.findUserExtrasForAccount(account).get(extraKey);
+ assertEquals(newExtraValue, newValue);
+
+ // Delete account and verify that extras cascade removed
+ mAccountsDb.deleteCeAccount(accId);
+ actualExtraId = mAccountsDb.findExtrasIdByAccountId(accId, extraKey);
+ assertEquals(-1, actualExtraId);
+ }
+
+ @Test
+ public void testGrantsInsertFindDelete() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ mAccountsDb.insertDeAccount(account, accId);
+ int testUid = 100500;
+ long grantId = mAccountsDb.insertGrant(accId, "tokenType", testUid);
+ assertTrue(grantId > 0);
+ List<Integer> allUidGrants = mAccountsDb.findAllUidGrants();
+ List<Integer> expectedUids = Arrays.asList(testUid);
+ assertEquals(expectedUids, allUidGrants);
+
+ long matchingGrantsCount = mAccountsDb.findMatchingGrantsCount(
+ testUid, "tokenType", account);
+ assertEquals(1, matchingGrantsCount);
+ // Test nonexistent type
+ matchingGrantsCount = mAccountsDb.findMatchingGrantsCount(
+ testUid, "noSuchType", account);
+ assertEquals(0, matchingGrantsCount);
+
+ matchingGrantsCount = mAccountsDb.findMatchingGrantsCountAnyToken(testUid, account);
+ assertEquals(1, matchingGrantsCount);
+
+ List<Pair<String, Integer>> allAccountGrants = mAccountsDb.findAllAccountGrants();
+ assertEquals(1, allAccountGrants.size());
+ assertEquals(account.name, allAccountGrants.get(0).first);
+ assertEquals(testUid, (int)allAccountGrants.get(0).second);
+
+ mAccountsDb.deleteGrantsByUid(testUid);
+ allUidGrants = mAccountsDb.findAllUidGrants();
+ assertTrue("Test grants should be removed", allUidGrants.isEmpty());
+ }
+
+ @Test
+ public void testSharedAccountsInsertFindDelete() {
+ Account account = new Account("name", "example.com");
+ long accId = 0;
+ mAccountsDb.insertDeAccount(account, accId);
+ long sharedAccId = mAccountsDb.insertSharedAccount(account);
+ long foundSharedAccountId = mAccountsDb.findSharedAccountId(account);
+ assertEquals(sharedAccId, foundSharedAccountId);
+ List<Account> sharedAccounts = mAccountsDb.getSharedAccounts();
+ List<Account> expectedList = Arrays.asList(account);
+ assertEquals(expectedList, sharedAccounts);
+
+ // Delete and verify
+ mAccountsDb.deleteSharedAccount(account);
+ foundSharedAccountId = mAccountsDb.findSharedAccountId(account);
+ assertEquals(-1, foundSharedAccountId);
+ }
+
+ @Test
+ public void testMetaInsertFindDelete() {
+ int testUid = 100500;
+ String authenticatorType = "authType";
+ mAccountsDb.insertOrReplaceMetaAuthTypeAndUid(authenticatorType, testUid);
+ Map<String, Integer> metaAuthUid = mAccountsDb.findMetaAuthUid();
+ assertEquals(1, metaAuthUid.size());
+ assertEquals(testUid, (int)metaAuthUid.get(authenticatorType));
+
+ // Delete and verify
+ boolean deleteResult = mAccountsDb.deleteMetaByAuthTypeAndUid(authenticatorType, testUid);
+ assertTrue(deleteResult);
+ metaAuthUid = mAccountsDb.findMetaAuthUid();
+ assertEquals(0, metaAuthUid.size());
+ }
+
+ @Test
+ public void testUpdateDeAccountLastAuthenticatedTime() {
+ Account account = new Account("name", "example.com");
+ long accId = 1;
+ mAccountsDb.insertDeAccount(account, accId);
+ long now = System.currentTimeMillis();
+ mAccountsDb.updateAccountLastAuthenticatedTime(account);
+ long time = mAccountsDb.findAccountLastAuthenticatedTime(account);
+ assertTrue("LastAuthenticatedTime should be current", time >= now);
+ }
+
+ @Test
+ public void testRenameAccount() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ mAccountsDb.insertDeAccount(account, accId);
+ mAccountsDb.renameDeAccount(accId, "newName", "name");
+ Account newAccount = mAccountsDb.findAllDeAccounts().get(accId);
+ assertEquals("newName", newAccount.name);
+
+ String prevName = mAccountsDb.findDeAccountPreviousName(newAccount);
+ assertEquals("name", prevName);
+ mAccountsDb.renameCeAccount(accId, "newName");
+ long foundAccId = mAccountsDb.findCeAccountId(account);
+ assertEquals("Account shouldn't be found under the old name", -1, foundAccId);
+ foundAccId = mAccountsDb.findCeAccountId(newAccount);
+ assertEquals(accId, foundAccId);
+ }
+
+ @Test
+ public void testUpdateCeAccountPassword() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ String newPassword = "newPassword";
+ mAccountsDb.updateCeAccountPassword(accId, newPassword);
+ String actualPassword = mAccountsDb
+ .findAccountPasswordByNameAndType(account.name, account.type);
+ assertEquals(newPassword, actualPassword);
+ }
+
+ @Test
+ public void testFindCeAccountsNotInDe() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ Account account = new Account("name", "example.com");
+ long accId = mAccountsDb.insertCeAccount(account, "password");
+ mAccountsDb.insertDeAccount(account, accId);
+
+ Account accountNotInDe = new Account("name2", "example.com");
+ mAccountsDb.insertCeAccount(accountNotInDe, "password");
+
+ List<Account> ceAccounts = mAccountsDb.findCeAccountsNotInDe();
+ List<Account> expectedList = Arrays.asList(accountNotInDe);
+ assertEquals(expectedList, ceAccounts);
+ }
+
+ @Test
+ public void testCrossDbTransactions() {
+ mAccountsDb.attachCeDatabase(ceDb);
+ mAccountsDb.beginTransaction();
+ Account account = new Account("name", "example.com");
+ long accId;
+ accId = mAccountsDb.insertCeAccount(account, "password");
+ accId = mAccountsDb.insertDeAccount(account, accId);
+ long actualId = mAccountsDb.findCeAccountId(account);
+ assertEquals(accId, actualId);
+ actualId = mAccountsDb.findDeAccountId(account);
+ assertEquals(accId, actualId);
+ mAccountsDb.endTransaction();
+ // Verify that records were removed
+ actualId = mAccountsDb.findCeAccountId(account);
+ assertEquals(-1, actualId);
+ actualId = mAccountsDb.findDeAccountId(account);
+ assertEquals(-1, actualId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java b/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java
new file mode 100644
index 0000000..fd2fb4b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java
@@ -0,0 +1,246 @@
+/*
+ * 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.am;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.res.Configuration;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test class for {@link ConfigurationContainer}. Mostly duplicates configuration tests from
+ * {@link com.android.server.wm.WindowContainerTests}.
+ *
+ * Build: mmma -j32 frameworks/base/services/tests/servicestests
+ * Install: adb install -r out/target/product/$TARGET_PRODUCT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -w -e class com.android.server.am.ConfigurationContainerTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ConfigurationContainerTests {
+
+ @Test
+ public void testConfigurationInit() throws Exception {
+ // Check root container initial config.
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ assertEquals(Configuration.EMPTY, root.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getConfiguration());
+
+ // Check child initial config.
+ final TestConfigurationContainer child1 = root.addChild();
+ assertEquals(Configuration.EMPTY, child1.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getConfiguration());
+
+ // Check child initial config if root has overrides.
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+ final TestConfigurationContainer child2 = root.addChild();
+ assertEquals(Configuration.EMPTY, child2.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getConfiguration());
+
+ // Check child initial config if root has parent config set.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.fontScale = 0.8f;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration rootFullConfig = new Configuration(rootParentConfig);
+ rootFullConfig.updateFrom(rootOverrideConfig);
+
+ final TestConfigurationContainer child3 = root.addChild();
+ assertEquals(Configuration.EMPTY, child3.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration());
+ assertEquals(rootFullConfig, child3.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangeOnAddRemove() throws Exception {
+ // Init root's config.
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init child's config.
+ final TestConfigurationContainer child = root.addChild();
+ 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(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());
+
+ // 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);
+ root2.addChild(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangePropagation() throws Exception {
+ // Builds 3-level vertical hierarchy with one configuration container on each level.
+ // In addition to different overrides on each level, everyone in hierarchy will have one
+ // common overridden value - orientation;
+
+ // Init root's config.
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init children.
+ final TestConfigurationContainer child1 = root.addChild();
+ final Configuration childOverrideConfig1 = new Configuration();
+ childOverrideConfig1.densityDpi = 320;
+ childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ child1.onOverrideConfigurationChanged(childOverrideConfig1);
+
+ final TestConfigurationContainer child2 = child1.addChild();
+ final Configuration childOverrideConfig2 = new Configuration();
+ childOverrideConfig2.screenWidthDp = 150;
+ childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT;
+ child2.onOverrideConfigurationChanged(childOverrideConfig2);
+
+ // Check configuration on all levels when root override is updated.
+ rootOverrideConfig.smallestScreenWidthDp = 200;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig);
+ mergedOverrideConfig1.updateFrom(childOverrideConfig1);
+ final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1);
+
+ final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1);
+ mergedOverrideConfig2.updateFrom(childOverrideConfig2);
+ final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+
+ // Check configuration on all levels when root parent config is updated.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.screenHeightDp = 100;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration mergedRootConfig = new Configuration(rootParentConfig);
+ mergedRootConfig.updateFrom(rootOverrideConfig);
+
+ mergedConfig1.setTo(mergedRootConfig);
+ mergedConfig1.updateFrom(mergedOverrideConfig1);
+
+ mergedConfig2.setTo(mergedConfig1);
+ mergedConfig2.updateFrom(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(mergedRootConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+ }
+
+ /**
+ * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed
+ * for testing.
+ */
+ private class TestConfigurationContainer
+ extends ConfigurationContainer<TestConfigurationContainer> {
+ private List<TestConfigurationContainer> mChildren = new ArrayList<>();
+ private TestConfigurationContainer mParent;
+
+ TestConfigurationContainer addChild(TestConfigurationContainer childContainer) {
+ childContainer.mParent = this;
+ childContainer.onParentChanged();
+ mChildren.add(childContainer);
+ return childContainer;
+ }
+
+ TestConfigurationContainer addChild() {
+ return addChild(new TestConfigurationContainer());
+ }
+
+ void removeChild(TestConfigurationContainer child) {
+ child.mParent = null;
+ child.onParentChanged();
+ }
+
+ @Override
+ protected int getChildCount() {
+ return mChildren.size();
+ }
+
+ @Override
+ protected TestConfigurationContainer getChildAt(int index) {
+ return mChildren.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mParent;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java
new file mode 100644
index 0000000..b47d17e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.am;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.ITaskStackListener;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TaskStackChangedListenerTest extends ITaskStackListener.Stub {
+
+ private IActivityManager mService;
+
+ private static final Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static boolean sTaskStackChangedCalled;
+ private static boolean sActivityBResumed;
+
+ @Before
+ public void setUp() throws Exception {
+ mService = ActivityManagerNative.getDefault();
+ mService.registerTaskStackListener(this);
+ }
+
+ @Test
+ public void testTaskStackChanged_afterFinish() throws Exception {
+ Context ctx = InstrumentationRegistry.getContext();
+ ctx.startActivity(new Intent(ctx, ActivityA.class));
+ UiDevice.getInstance(getInstrumentation()).waitForIdle();
+ synchronized (sLock) {
+ Assert.assertTrue(sTaskStackChangedCalled);
+ }
+ Assert.assertTrue(sActivityBResumed);
+ }
+
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ synchronized (sLock) {
+ sTaskStackChangedCalled = true;
+ }
+ }
+
+ @Override
+ public void onActivityPinned() throws RemoteException {
+ }
+
+ @Override
+ public void onPinnedActivityRestartAttempt() throws RemoteException {
+ }
+
+ @Override
+ public void onPinnedStackAnimationEnded() throws RemoteException {
+ }
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId) throws RemoteException {
+ }
+
+ @Override
+ public void onActivityDismissingDockedStack() throws RemoteException {
+ }
+
+ public static class ActivityA extends Activity {
+
+ private boolean mActivityBLaunched = false;
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ if (mActivityBLaunched) {
+ return;
+ }
+ mActivityBLaunched = true;
+ finish();
+ startActivity(new Intent(this, ActivityB.class));
+ }
+ }
+
+ public static class ActivityB extends Activity {
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ synchronized (sLock) {
+ sTaskStackChangedCalled = false;
+ }
+ sActivityBResumed = true;
+ finish();
+ }
+ }
+}
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..6ff0c5a 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -16,6 +16,17 @@
package com.android.server.connectivity;
+import static com.android.server.connectivity.MetricsTestUtil.aBool;
+import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
+import static com.android.server.connectivity.MetricsTestUtil.aLong;
+import static com.android.server.connectivity.MetricsTestUtil.aString;
+import static com.android.server.connectivity.MetricsTestUtil.aType;
+import static com.android.server.connectivity.MetricsTestUtil.anInt;
+import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
+import static com.android.server.connectivity.MetricsTestUtil.b;
+import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
+import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog;
+
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
@@ -28,21 +39,10 @@
import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
-import com.google.protobuf.nano.MessageNano;
-import java.util.Arrays;
+
import junit.framework.TestCase;
-import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog;
-import static com.android.server.connectivity.MetricsTestUtil.aBool;
-import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
-import static com.android.server.connectivity.MetricsTestUtil.aLong;
-import static com.android.server.connectivity.MetricsTestUtil.aString;
-import static com.android.server.connectivity.MetricsTestUtil.aType;
-import static com.android.server.connectivity.MetricsTestUtil.anInt;
-import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
-import static com.android.server.connectivity.MetricsTestUtil.b;
-import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
-import static com.android.server.connectivity.MetricsTestUtil.ipEv;
+import java.util.Arrays;
public class IpConnectivityEventBuilderTest extends TestCase {
@@ -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);
}
@@ -340,8 +351,7 @@
static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
try {
byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input));
- IpConnectivityLog log = new IpConnectivityLog();
- MessageNano.mergeFrom(log, got);
+ IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString());
} catch (Exception e) {
fail(e.toString());
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..c7982b1 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -16,9 +16,13 @@
package com.android.server.connectivity;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
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;
@@ -29,22 +33,21 @@
import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable;
import android.util.Base64;
+
import com.android.server.connectivity.metrics.IpConnectivityLogClass;
-import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
-import junit.framework.TestCase;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
public class IpConnectivityMetricsTest extends TestCase {
static final IpReachabilityEvent FAKE_EV =
@@ -57,7 +60,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mService = new IpConnectivityMetrics(mCtx);
+ mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
}
public void testLoggingEvents() throws Exception {
@@ -112,6 +115,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 +228,8 @@
" router_lifetime: 2000",
" >",
" time_ms: 700",
- ">");
+ ">",
+ "version: 2");
verifySerialization(want, getdump("flush"));
}
@@ -231,8 +256,7 @@
try {
byte[] got = Base64.decode(output, Base64.DEFAULT);
IpConnectivityLogClass.IpConnectivityLog log =
- new IpConnectivityLogClass.IpConnectivityLog();
- MessageNano.mergeFrom(log, got);
+ IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString());
} catch (Exception e) {
fail(e.toString());
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 63d5d9fb..9e2fd62 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -158,7 +158,8 @@
void log(int netId, int[] latencies) {
for (int l : latencies) {
- mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l);
+ mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null, 0,
+ 0);
}
}
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/connectivity/VpnTest.java b/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java
index 5d8b843..b51b277 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java
@@ -25,9 +25,11 @@
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.UidRange;
import android.os.INetworkManagementService;
import android.os.Looper;
@@ -43,6 +45,8 @@
import java.util.Map;
import java.util.Set;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -88,14 +92,18 @@
@Mock private PackageManager mPackageManager;
@Mock private INetworkManagementService mNetService;
@Mock private AppOpsManager mAppOps;
+ @Mock private NotificationManager mNotificationManager;
@Override
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(mPackages);
+ when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
+ when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
+ .thenReturn(mNotificationManager);
doNothing().when(mNetService).registerObserver(any());
}
@@ -103,7 +111,7 @@
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
- final Vpn vpn = new MockVpn(primaryUser.id);
+ final Vpn vpn = spyVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
@@ -117,7 +125,7 @@
public void testManagedProfilesAreNotAddedToVpn() {
setMockedUsers(primaryUser, managedProfileA);
- final Vpn vpn = new MockVpn(primaryUser.id);
+ final Vpn vpn = spyVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
@@ -130,7 +138,7 @@
public void testAddUserToVpnOnlyAddsOneUser() {
setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
- final Vpn vpn = new MockVpn(primaryUser.id);
+ final Vpn vpn = spyVpn(primaryUser.id);
final Set<UidRange> ranges = new ArraySet<>();
vpn.addUserToRanges(ranges, primaryUser.id, null, null);
@@ -141,7 +149,7 @@
@SmallTest
public void testUidWhiteAndBlacklist() throws Exception {
- final Vpn vpn = new MockVpn(primaryUser.id);
+ final Vpn vpn = spyVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
@@ -166,15 +174,15 @@
@SmallTest
public void testLockdownChangingPackage() throws Exception {
- final MockVpn vpn = new MockVpn(primaryUser.id);
+ final Vpn vpn = spyVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
// Default state.
- vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
- vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
@@ -182,8 +190,8 @@
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
}));
- vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
- vpn.assertUnblocked(user.start + PKG_UIDS[1]);
+ assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[1]);
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
@@ -195,13 +203,13 @@
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
}));
- vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
- vpn.assertUnblocked(user.start + PKG_UIDS[3]);
+ assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[3]);
}
@SmallTest
public void testLockdownAddingAProfile() throws Exception {
- final MockVpn vpn = new MockVpn(primaryUser.id);
+ final Vpn vpn = spyVpn(primaryUser.id);
setMockedUsers(primaryUser);
// Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
@@ -220,7 +228,7 @@
}));
// Verify restricted user isn't affected at first.
- vpn.assertUnblocked(profile.start + PKG_UIDS[0]);
+ assertUnblocked(vpn, profile.start + PKG_UIDS[0]);
// Add the restricted user.
setMockedUsers(primaryUser, tempProfile);
@@ -239,24 +247,53 @@
}));
}
+ @SmallTest
+ public void testNotificationShownForAlwaysOnApp() {
+ final Vpn vpn = spyVpn(primaryUser.id);
+ final InOrder order = inOrder(vpn);
+ setMockedUsers(primaryUser);
+
+ // Don't show a notification for regular disconnected states.
+ vpn.updateState(DetailedState.DISCONNECTED, TAG);
+ order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+
+ // Start showing a notification for disconnected once always-on.
+ vpn.setAlwaysOnPackage(PKGS[0], false);
+ order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+
+ // Stop showing the notification once connected.
+ vpn.updateState(DetailedState.CONNECTED, TAG);
+ order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+
+ // Show the notification if we disconnect again.
+ vpn.updateState(DetailedState.DISCONNECTED, TAG);
+ order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+
+ // Notification should be cleared after unsetting always-on package.
+ vpn.setAlwaysOnPackage(null, false);
+ order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ }
+
/**
- * A subclass of {@link Vpn} with some of the fields pre-mocked.
+ * Mock some methods of vpn object.
*/
- private class MockVpn extends Vpn {
- public MockVpn(@UserIdInt int userId) {
- super(Looper.myLooper(), mContext, mNetService, userId);
- }
+ private Vpn spyVpn(@UserIdInt int userId) {
+ final Vpn vpn = spy(new Vpn(Looper.myLooper(), mContext, mNetService, userId));
- public void assertBlocked(int... uids) {
- for (int uid : uids) {
- assertTrue("Uid " + uid + " should be blocked", isBlockingUid(uid));
- }
- }
+ // Block calls to the NotificationManager or PendingIntent#getActivity.
+ doNothing().when(vpn).updateAlwaysOnNotificationInternal(anyBoolean());
+ return vpn;
+ }
- public void assertUnblocked(int... uids) {
- for (int uid : uids) {
- assertFalse("Uid " + uid + " should not be blocked", isBlockingUid(uid));
- }
+ private static void assertBlocked(Vpn vpn, int... uids) {
+ for (int uid : uids) {
+ assertTrue("Uid " + uid + " should be blocked", vpn.isBlockingUid(uid));
+ }
+ }
+
+ private static void assertUnblocked(Vpn vpn, int... uids) {
+ for (int uid : uids) {
+ assertFalse("Uid " + uid + " should not be blocked", vpn.isBlockingUid(uid));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 01b2c3b..56ff621 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1915,6 +1915,61 @@
verifyScreenTimeoutCall(Integer.MAX_VALUE, false);
}
+ public void testSetRequiredStrongAuthTimeout_DeviceOwner() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+
+ final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
+ final long ONE_MINUTE = 60 * 1000;
+
+ // aggregation should be the default if unset by any admin
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null),
+ DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
+
+ // admin not participating by default
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), 0);
+
+ //clamping from the top
+ dpm.setRequiredStrongAuthTimeout(admin1,
+ DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS + ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1),
+ DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null),
+ DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
+
+ // 0 means default
+ dpm.setRequiredStrongAuthTimeout(admin1, 0);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), 0);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null),
+ DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
+
+ // clamping from the bottom
+ dpm.setRequiredStrongAuthTimeout(admin1, MINIMUM_STRONG_AUTH_TIMEOUT_MS - ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MINIMUM_STRONG_AUTH_TIMEOUT_MS);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null), MINIMUM_STRONG_AUTH_TIMEOUT_MS);
+
+ // value within range
+ dpm.setRequiredStrongAuthTimeout(admin1, MINIMUM_STRONG_AUTH_TIMEOUT_MS + ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MINIMUM_STRONG_AUTH_TIMEOUT_MS
+ + ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null), MINIMUM_STRONG_AUTH_TIMEOUT_MS
+ + ONE_MINUTE);
+
+ // reset to default
+ dpm.setRequiredStrongAuthTimeout(admin1, 0);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), 0);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null),
+ DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
+
+ // negative value
+ try {
+ dpm.setRequiredStrongAuthTimeout(admin1, -ONE_MINUTE);
+ fail("Didn't throw IllegalArgumentException");
+ } catch (IllegalArgumentException iae) {
+ }
+ }
+
private void verifyScreenTimeoutCall(Integer expectedTimeout,
boolean shouldStayOnWhilePluggedInBeCleared) {
if (expectedTimeout == null) {
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/notification/SnoozeHelperTest.java b/services/tests/servicestests/src/com/android/server/notification/SnoozeHelperTest.java
new file mode 100644
index 0000000..ec1fdad
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 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 static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SnoozeHelperTest {
+ @Mock SnoozeHelper.Callback mCallback;
+ @Mock AlarmManager mAm;
+ @Mock ManagedServices.UserProfiles mUserProfiles;
+
+ private SnoozeHelper mSnoozeHelper;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mSnoozeHelper = new SnoozeHelper(getContext(), mCallback, mUserProfiles);
+ mSnoozeHelper.setAlarmManager(mAm);
+ }
+
+ @Test
+ public void testSnooze() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
+ verify(mAm, times(1)).setExactAndAllowWhileIdle(
+ anyInt(), eq((long) 1000), any(PendingIntent.class));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ }
+
+ @Test
+ public void testCancelByApp() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
+ mSnoozeHelper.snooze(r2 , UserHandle.USER_SYSTEM, 1000);
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+
+ mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1);
+ // 3 = one for each snooze, above + one for cancel itself.
+ verify(mAm, times(3)).cancel(any(PendingIntent.class));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+ }
+
+ @Test
+ public void testCancelAllForUser() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg", 3, "three", UserHandle.ALL);
+ mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
+ mSnoozeHelper.snooze(r2 , UserHandle.USER_SYSTEM, 1000);
+ mSnoozeHelper.snooze(r3 , UserHandle.USER_ALL, 1000);
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_ALL, r3.sbn.getPackageName(), r3.getKey()));
+
+ mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, false);
+ // 5 = once for each snooze above (3) + once for each notification canceled (2).
+ verify(mAm, times(5)).cancel(any(PendingIntent.class));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_ALL, r3.sbn.getPackageName(), r3.getKey()));
+ }
+
+ @Test
+ public void testCancelAllByApp() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
+ mSnoozeHelper.snooze(r2 , UserHandle.USER_SYSTEM, 1000);
+ mSnoozeHelper.snooze(r3 , UserHandle.USER_SYSTEM, 1000);
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+
+ mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, "pkg2");
+ // 4 = once for each snooze above (3) + once for each notification canceled (1).
+ verify(mAm, times(4)).cancel(any(PendingIntent.class));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+ }
+
+ @Test
+ public void testRepost() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
+ mSnoozeHelper.snooze(r2 , UserHandle.USER_ALL, 1000);
+ mSnoozeHelper.repost(r.sbn.getPackageName(), r.getKey(), UserHandle.USER_SYSTEM);
+ verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
+ r.getNotification().category = "NEW CATEGORY";
+
+ mSnoozeHelper.update(UserHandle.USER_SYSTEM, r);
+ verify(mCallback, never()).repost(anyInt(), any(NotificationRecord.class));
+
+ mSnoozeHelper.repost(r.sbn.getPackageName(), r.getKey(), UserHandle.USER_SYSTEM);
+ verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
+ }
+
+ private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
+ UserHandle user) {
+ Notification n = new Notification.Builder(getContext())
+ .setContentTitle("A")
+ .setGroup("G")
+ .setSortKey("A")
+ .setWhen(1205)
+ .build();
+ return new NotificationRecord(getContext(), new StatusBarNotification(
+ pkg, pkg, id, tag, 0, 0, 0, n, user),
+ new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name"));
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 53c6a33..bd2bb6c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -115,14 +115,14 @@
Settings settings =
new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
assertThat(settings.readLPw(createFakeUsers()), is(true));
- assertThat(settings.peekPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
- assertThat(settings.peekPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
+ assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
+ assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
- PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_1);
+ PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_1);
assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DEFAULT));
assertThat(ps.getNotLaunched(0), is(true));
- ps = settings.peekPackageLPr(PACKAGE_NAME_2);
+ ps = settings.getPackageLPr(PACKAGE_NAME_2);
assertThat(ps.getStopped(0), is(false));
assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
@@ -141,7 +141,7 @@
settings = new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
assertThat(settings.readLPw(createFakeUsers()), is(true));
- PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2);
+ PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
}
@@ -155,7 +155,7 @@
assertThat(settings.readLPw(createFakeUsers()), is(true));
// Enable/Disable a package
- PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_1);
+ PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_1);
ps.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0, null);
ps.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 1, null);
assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
index 3c99174..25f9100 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
@@ -68,7 +68,7 @@
/* fdin*/ null,
/* fdout*/ fd.getFileDescriptor(),
/* fderr*/ fd.getFileDescriptor(),
- args, rr);
+ args, null, rr);
}
return readAll(out);
} finally {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 6a434ca..1f0422b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.app.ActivityManager;
import android.os.Bundle;
@@ -46,16 +47,24 @@
private static final int REMOVE_TIMEOUT_MILLIS = 60 * 1000; // 60 seconds
private static final int SWITCH_USER_TIMEOUT_MILLIS = 40 * 1000; // 40 seconds
+ // Packages which are used during tests.
+ private static final String[] PACKAGES = new String[] {
+ "com.android.egg",
+ "com.android.retaildemo"
+ };
+
private final Object mUserRemoveLock = new Object();
private final Object mUserSwitchLock = new Object();
private UserManager mUserManager = null;
+ private PackageManager mPackageManager;
private List<Integer> usersToRemove;
@Override
public void setUp() throws Exception {
super.setUp();
mUserManager = UserManager.get(getContext());
+ mPackageManager = getContext().getPackageManager();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -185,6 +194,81 @@
assertFalse(mUserManager.isManagedProfile());
}
+ // Verify that disallowed packages are not installed in the managed profile.
+ @MediumTest
+ public void testAddManagedProfile_withDisallowedPackages() throws Exception {
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ UserInfo userInfo1 = createProfileForUser("Managed1",
+ UserInfo.FLAG_MANAGED_PROFILE, primaryUserId);
+ // Verify that the packagesToVerify are installed by default.
+ for (String pkg : PACKAGES) {
+ assertTrue("Package should be installed in managed profile: " + pkg,
+ isPackageInstalledForUser(pkg, userInfo1.id));
+ }
+ removeUser(userInfo1.id);
+
+ UserInfo userInfo2 = createProfileForUser("Managed2",
+ UserInfo.FLAG_MANAGED_PROFILE, primaryUserId, PACKAGES);
+ // Verify that the packagesToVerify are not installed by default.
+ for (String pkg : PACKAGES) {
+ assertFalse("Package should not be installed in managed profile when disallowed: "
+ + pkg, isPackageInstalledForUser(pkg, userInfo2.id));
+ }
+ }
+
+ // Verify that if any packages are disallowed to install during creation of managed profile can
+ // still be installed later.
+ @MediumTest
+ public void testAddManagedProfile_disallowedPackagesInstalledLater() throws Exception {
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ UserInfo userInfo = createProfileForUser("Managed",
+ UserInfo.FLAG_MANAGED_PROFILE, primaryUserId, PACKAGES);
+ // Verify that the packagesToVerify are not installed by default.
+ for (String pkg : PACKAGES) {
+ assertFalse("Package should not be installed in managed profile when disallowed: "
+ + pkg, isPackageInstalledForUser(pkg, userInfo.id));
+ }
+
+ // Verify that the disallowed packages during profile creation can be installed now.
+ for (String pkg : PACKAGES) {
+ assertEquals("Package could not be installed: " + pkg,
+ PackageManager.INSTALL_SUCCEEDED,
+ mPackageManager.installExistingPackageAsUser(pkg, userInfo.id));
+ }
+ }
+
+ // Make sure createProfile would fail if we have DISALLOW_ADD_USER.
+ @MediumTest
+ public void testCreateProfileForUser_disallowAddUser() throws Exception {
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle);
+ try {
+ UserInfo userInfo = createProfileForUser("Managed",
+ UserInfo.FLAG_MANAGED_PROFILE, primaryUserId);
+ assertNull(userInfo);
+ } finally {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
+ primaryUserHandle);
+ }
+ }
+
+ // Make sure createProfileEvenWhenDisallowedForUser bypass DISALLOW_ADD_USER.
+ @MediumTest
+ public void testCreateProfileForUserEvenWhenDisallowed() throws Exception {
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle);
+ try {
+ UserInfo userInfo = createProfileEvenWhenDisallowedForUser("Managed",
+ UserInfo.FLAG_MANAGED_PROFILE, primaryUserId);
+ assertNotNull(userInfo);
+ } finally {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
+ primaryUserHandle);
+ }
+ }
+
@MediumTest
public void testAddRestrictedProfile() throws Exception {
UserInfo userInfo = createRestrictedProfile("Profile");
@@ -357,6 +441,14 @@
switchUser(startUser);
}
+ private boolean isPackageInstalledForUser(String packageName, int userId) {
+ try {
+ return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
private void switchUser(int userId) {
synchronized (mUserSwitchLock) {
ActivityManager am = getContext().getSystemService(ActivityManager.class);
@@ -401,7 +493,23 @@
}
private UserInfo createProfileForUser(String name, int flags, int userHandle) {
- UserInfo profile = mUserManager.createProfileForUser(name, flags, userHandle);
+ return createProfileForUser(name, flags, userHandle, null);
+ }
+
+ private UserInfo createProfileForUser(String name, int flags, int userHandle,
+ String[] disallowedPackages) {
+ UserInfo profile = mUserManager.createProfileForUser(
+ name, flags, userHandle, disallowedPackages);
+ if (profile != null) {
+ usersToRemove.add(profile.id);
+ }
+ return profile;
+ }
+
+ private UserInfo createProfileEvenWhenDisallowedForUser(String name, int flags,
+ int userHandle) {
+ UserInfo profile = mUserManager.createProfileForUserEvenWhenDisallowed(
+ name, flags, userHandle, null);
if (profile != null) {
usersToRemove.add(profile.id);
}
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
index 56a170c..e4473ad 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -46,6 +46,7 @@
import android.content.res.Configuration;
import android.media.AudioManager;
import android.net.Uri;
+import android.net.wifi.WifiManager;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
@@ -62,7 +63,6 @@
import com.android.internal.util.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.retaildemo.RetailDemoModeService.Injector;
@@ -75,7 +75,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -97,6 +96,7 @@
private @Mock NotificationManager mNm;
private @Mock ActivityManagerInternal mAmi;
private @Mock AudioManager mAudioManager;
+ private @Mock WifiManager mWifiManager;
private @Mock LockPatternUtils mLockPatternUtils;
private MockPreloadAppsInstaller mPreloadAppsInstaller;
private MockContentResolver mContentResolver;
@@ -135,9 +135,6 @@
@After
public void tearDown() {
- // Remove the RetailDemoModeServiceInternal from LocalServices which would've been
- // added during initialization of RetailDemoModeService in setUp().
- LocalServices.removeServiceForTest(RetailDemoModeServiceInternal.class);
FileUtils.deleteContentsAndDir(mTestPreloadsDir);
}
@@ -145,23 +142,22 @@
public void testDemoUserSetup() throws Exception {
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+ mLatch = new CountDownLatch(1);
+ final UserInfo userInfo = new UserInfo();
+ userInfo.id = TEST_DEMO_USER;
+ when(mUm.createUser(anyString(), anyInt())).thenReturn(userInfo);
+
+ setCameraPackage(TEST_CAMERA_PKG);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ assertEquals(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED + " property not set",
+ "1", mInjector.systemPropertiesGet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED));
+
final ArgumentCaptor<IntentFilter> intentFilter =
ArgumentCaptor.forClass(IntentFilter.class);
verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
assertTrue("Not registered for " + Intent.ACTION_SCREEN_OFF,
intentFilter.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
- mLatch = new CountDownLatch(1);
-
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
- assertEquals(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED + " property not set",
- "1", mInjector.systemPropertiesGet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED));
-
- final UserInfo userInfo = new UserInfo();
- userInfo.id = TEST_DEMO_USER;
- when(mUm.createUser(anyString(), anyInt())).thenReturn(userInfo);
- mInjector.setDemoUserId(TEST_DEMO_USER);
- setCameraPackage(TEST_CAMERA_PKG);
// Wait for the setup to complete.
mLatch.await(SETUP_COMPLETE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
ArgumentCaptor<Integer> flags = ArgumentCaptor.forClass(Integer.class);
@@ -197,7 +193,36 @@
}
@Test
- public void testSettingsObserver() throws Exception {
+ public void testSettingsObserver_disableDemoMode() throws Exception {
+ final RetailDemoModeService.SettingsObserver observer =
+ mService.new SettingsObserver(new Handler(Looper.getMainLooper()));
+ final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
+ when(mUm.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT, UserHandle.SYSTEM))
+ .thenReturn(false);
+ Settings.Global.putInt(mContentResolver, Settings.Global.PACKAGE_VERIFIER_ENABLE, 1);
+ // Settings.Global.DEVICE_DEMO_MODE has been set to 1 initially.
+ observer.onChange(false, deviceDemoModeUri);
+ final ArgumentCaptor<BroadcastReceiver> receiver =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+ Settings.Global.putInt(mContentResolver, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+ new File(mTestPreloadsDir, "dir1").mkdirs();
+ new File(mTestPreloadsDir, "file1").createNewFile();
+ Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_DEMO_MODE, 0);
+ observer.onChange(false, deviceDemoModeUri);
+ verify(mContext).unregisterReceiver(receiver.getValue());
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, false, UserHandle.SYSTEM);
+ assertEquals("Package verifier enable value has not been reset", 1,
+ Settings.Global.getInt(mContentResolver, Settings.Global.PACKAGE_VERIFIER_ENABLE));
+ Thread.sleep(20); // Wait for the deletion to complete.
+ // verify that the preloaded directory is emptied.
+ assertEquals("Preloads directory is not emptied",
+ 0, mTestPreloadsDir.list().length);
+ }
+
+ @Test
+ public void testSettingsObserver_enableDemoMode() throws Exception {
final RetailDemoModeService.SettingsObserver observer =
mService.new SettingsObserver(new Handler(Looper.getMainLooper()));
final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
@@ -206,14 +231,11 @@
assertEquals(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED + " property not set",
"1", mInjector.systemPropertiesGet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED));
- new File(mTestPreloadsDir, "dir1").mkdirs();
- new File(mTestPreloadsDir, "file1").createNewFile();
- Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_DEMO_MODE, 0);
- observer.onChange(false, deviceDemoModeUri);
- Thread.sleep(20); // Wait for the deletion to complete.
- // verify that the preloaded directory is emptied.
- assertEquals("Preloads directory is not emptied",
- 0, mTestPreloadsDir.list().length);
+ final ArgumentCaptor<IntentFilter> intentFilter =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
+ assertTrue("Not registered for " + Intent.ACTION_SCREEN_OFF,
+ intentFilter.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
}
@Test
@@ -227,6 +249,7 @@
final UserInfo userInfo = new UserInfo(TEST_DEMO_USER, "demo_user",
UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
when(mUm.getUserInfo(TEST_DEMO_USER)).thenReturn(userInfo);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
final int minVolume = -111;
for (int stream : RetailDemoModeService.VOLUME_STREAMS_TO_MUTE) {
when(mAudioManager.getStreamMinVolume(stream)).thenReturn(minVolume);
@@ -238,6 +261,7 @@
verify(mAudioManager).setStreamVolume(stream, minVolume, 0);
}
verify(mLockPatternUtils).setLockScreenDisabled(true, TEST_DEMO_USER);
+ verify(mWifiManager).setWifiEnabled(true);
}
private void setCameraPackage(String pkgName) {
@@ -304,7 +328,6 @@
private class TestInjector extends Injector {
private ArrayMap<String, String> mSystemProperties = new ArrayMap<>();
- private int mDemoUserId = UserHandle.USER_NULL;
TestInjector() {
super(mContext);
@@ -321,6 +344,11 @@
}
@Override
+ WifiManager getWifiManager() {
+ return mWifiManager;
+ }
+
+ @Override
void switchUser(int userId) {
if (mLatch != null) {
mLatch.countDown();
@@ -376,6 +404,10 @@
}
@Override
+ void destroyWakeLock() {
+ }
+
+ @Override
boolean isWakeLockHeld() {
return false;
}
@@ -416,12 +448,13 @@
return mTestPreloadsDir;
}
- String systemPropertiesGet(String key) {
- return mSystemProperties.get(key);
+ @Override
+ void publishLocalService(RetailDemoModeService service,
+ RetailDemoModeServiceInternal localService) {
}
- void setDemoUserId(int userId) {
- mDemoUserId = userId;
+ String systemPropertiesGet(String key) {
+ return mSystemProperties.get(key);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
new file mode 100644
index 0000000..d378b7c56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
@@ -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.
+ */
+
+package com.android.server.utils;
+
+import static com.android.server.utils.PriorityDump.dump;
+
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import com.android.server.utils.PriorityDump.PriorityDumper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PriorityDumpTest {
+
+ private static final String[] EMPTY_ARGS = {};
+
+ @Mock
+ private PriorityDumper mDumper;
+ @Mock
+ private PrintWriter mPw;
+
+ private final FileDescriptor mFd = FileDescriptor.err;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNullArgs() {
+ dump(mDumper, mFd, mPw, null);
+ verify(mDumper).dump(same(mFd), same(mPw), eq(null));
+ }
+
+ @Test
+ public void testNoArgs() {
+ dump(mDumper, mFd, mPw, EMPTY_ARGS);
+ verify(mDumper).dump(same(mFd), same(mPw), same(EMPTY_ARGS));
+ }
+
+ @Test
+ public void testNonPriorityArgs() {
+ final String[] args = {
+ "--dumb_priority"
+ };
+ dump(mDumper, mFd, mPw, args);
+ verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ }
+
+ @Test
+ public void testMissingPriority() {
+ final String[] args = {
+ "--dump_priority"
+ };
+ dump(mDumper, mFd, mPw, args);
+ verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ }
+
+ @Test
+ public void testInvalidPriorityNoExtraArgs() {
+ final String[] args = {
+ "--dump_priority", "SUPER_HIGH"
+ };
+ dump(mDumper, mFd, mPw, args);
+ verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ }
+
+ @Test
+ public void testInvalidPriorityExtraArgs() {
+ final String[] args = {
+ "--dump_priority", "SUPER_HIGH", "--high", "--five"
+ };
+ dump(mDumper, mFd, mPw, args);
+ verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ }
+
+ @Test
+ public void testNoPriorityCallsAllMethods() {
+ final String[] args = {
+ "1", "2", "3"
+ };
+
+ // Cannot use mDumper here because it would mock the dump() call.
+ final FakeDumper fakeDumper = new FakeDumper();
+
+ dump(fakeDumper, mFd, mPw, args);
+
+ assertSame(mFd, fakeDumper.criticalFd);
+ assertSame(mPw, fakeDumper.criticalPw);
+ assertSame(args, fakeDumper.criticalArgs);
+ assertSame(mFd, fakeDumper.highFd);
+ assertSame(mPw, fakeDumper.highPw);
+ assertSame(args, fakeDumper.highArgs);
+ assertSame(mFd, fakeDumper.normalFd);
+ assertSame(mPw, fakeDumper.normalPw);
+ assertSame(args, fakeDumper.normalArgs);
+ }
+
+ @Test
+ public void testCriticalNoExtraArgs() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--dump_priority", "CRITICAL"
+ });
+ verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(EMPTY_ARGS));
+ }
+
+ @Test
+ public void testCriticalExtraArgs() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--dump_priority", "CRITICAL", "--high", "--five"
+ });
+ verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
+ "--high", "--five"
+ }));
+ }
+
+ @Test
+ public void testHighNoExtraArgs() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--dump_priority", "HIGH"
+ });
+ verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(EMPTY_ARGS));
+ }
+
+ @Test
+ public void testHighExtraArgs() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--dump_priority", "HIGH", "--high", "--five"
+ });
+ verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(new String[] {
+ "--high", "--five"
+ }));
+ }
+
+ @Test
+ public void testNormalNoExtraArgs() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--dump_priority", "NORMAL"
+ });
+ verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(EMPTY_ARGS));
+ }
+
+ @Test
+ public void testNormalExtraArgs() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--dump_priority", "NORMAL", "--high", "--five"
+ });
+ verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(new String[] {
+ "--high", "--five"
+ }));
+ }
+
+ private final class FakeDumper implements PriorityDumper {
+
+ String[] criticalArgs, highArgs, normalArgs;
+ FileDescriptor criticalFd, highFd, normalFd;
+ PrintWriter criticalPw, highPw, normalPw;
+
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ criticalFd = fd;
+ criticalPw = pw;
+ criticalArgs = args;
+ }
+
+ @Override
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+ highFd = fd;
+ highPw = pw;
+ highArgs = args;
+ }
+
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ normalFd = fd;
+ normalPw = pw;
+ normalArgs = args;
+ }
+ }
+}
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/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 7f171fb..0f898e5 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -303,6 +303,31 @@
WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
+ assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
+
+ // Now install a package
+ String singlePackage = "singlePackage";
+ packages = new WebViewProviderInfo[]{
+ new WebViewProviderInfo(singlePackage, "", true, false, null)};
+ setupWithPackages(packages);
+ setEnabledAndValidPackageInfos(packages);
+
+ mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
+ WebViewUpdateService.PACKAGE_ADDED, 0);
+
+ checkPreparationPhasesForPackage(singlePackage, 1 /* number of finished preparations */);
+ assertEquals(singlePackage,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+
+ // Remove the package again
+ mTestSystemImpl.removePackageInfo(singlePackage);
+ mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
+ WebViewUpdateService.PACKAGE_ADDED, 0);
+
+ // Package removed - ensure our interface states that there is no package
+ response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+ assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
+ assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
}
public void testFailListingInvalidWebviewPackage() {
@@ -395,7 +420,8 @@
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
- assertEquals(firstPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackageName());
+ assertEquals(firstPackage,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
new Thread(new Runnable() {
@Override
@@ -1243,4 +1269,42 @@
checkPreparationPhasesForPackage(primaryPackage, 3 /* third preparation phase */);
}
+
+ public void testGetCurrentWebViewPackage() {
+ PackageInfo firstPackage = createPackageInfo("first", true /* enabled */,
+ true /* valid */, true /* installed */);
+ firstPackage.versionCode = 100;
+ firstPackage.versionName = "first package version";
+ WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+ new WebViewProviderInfo(firstPackage.packageName, "", true, false, null)};
+ setupWithPackages(packages, true);
+ mTestSystemImpl.setPackageInfo(firstPackage);
+
+ mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+
+ Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
+ Mockito.argThat(new IsPackageInfoWithName(firstPackage.packageName)));
+
+ mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
+
+ // Ensure the API is correct before running waitForAndGetProvider
+ assertEquals(firstPackage.packageName,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+ assertEquals(firstPackage.versionCode,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionCode);
+ assertEquals(firstPackage.versionName,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName);
+
+ WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+ assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status);
+ assertEquals(firstPackage.packageName, response.packageInfo.packageName);
+
+ // Ensure the API is still correct after running waitForAndGetProvider
+ assertEquals(firstPackage.packageName,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+ assertEquals(firstPackage.versionCode,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionCode);
+ assertEquals(firstPackage.versionName,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 27d9d10..74e8c9d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -76,15 +76,14 @@
private WindowState createWindow(WindowState parent, int type, WindowToken token) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
- return new WindowState(sWm, null, mIWindow, token, parent, 0, 0, attrs, 0,
- sWm.getDefaultDisplayContentLocked(), 0);
+ return new WindowState(sWm, null, mIWindow, token, parent, 0, 0, attrs, 0, 0);
}
/* Used so we can gain access to some protected members of the {@link AppWindowToken} class */
private class TestAppWindowToken extends AppWindowToken {
TestAppWindowToken() {
- super(sWm, null, false);
+ super(sWm, null, false, sWm.getDefaultDisplayContentLocked());
}
int getWindowsCount() {
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 eb2372a..128317c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -19,6 +19,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.content.res.Configuration;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -28,6 +29,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static org.junit.Assert.assertEquals;
@@ -377,6 +380,168 @@
assertEquals(1, child2223.compareTo(child21));
}
+ @Test
+ public void testConfigurationInit() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+
+ // Check root container initial config.
+ final TestWindowContainer root = builder.setLayer(0).build();
+ assertEquals(Configuration.EMPTY, root.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getConfiguration());
+
+ // Check child initial config.
+ final TestWindowContainer child1 = root.addChildWindow();
+ assertEquals(Configuration.EMPTY, child1.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getConfiguration());
+
+ // Check child initial config if root has overrides.
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+ final TestWindowContainer child2 = root.addChildWindow();
+ assertEquals(Configuration.EMPTY, child2.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getConfiguration());
+
+ // Check child initial config if root has parent config set.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.fontScale = 0.8f;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration rootFullConfig = new Configuration(rootParentConfig);
+ rootFullConfig.updateFrom(rootOverrideConfig);
+
+ final TestWindowContainer child3 = root.addChildWindow();
+ assertEquals(Configuration.EMPTY, child3.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration());
+ assertEquals(rootFullConfig, child3.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangeOnAddRemove() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+
+ // Init root's config.
+ final TestWindowContainer root = builder.setLayer(0).build();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init child's config.
+ final TestWindowContainer child = root.addChildWindow();
+ 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 - it should remain same.
+ root.removeChild(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ 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());
+
+ // 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);
+ root2.addChildWindow(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangePropagation() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+
+ // Builds 3-level vertical hierarchy with one window container on each level.
+ // In addition to different overrides on each level, everyone in hierarchy will have one
+ // common overridden value - orientation;
+
+ // Init root's config.
+ final TestWindowContainer root = builder.setLayer(0).build();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init children.
+ final TestWindowContainer child1 = root.addChildWindow();
+ final Configuration childOverrideConfig1 = new Configuration();
+ childOverrideConfig1.densityDpi = 320;
+ childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ child1.onOverrideConfigurationChanged(childOverrideConfig1);
+
+ final TestWindowContainer child2 = child1.addChildWindow();
+ final Configuration childOverrideConfig2 = new Configuration();
+ childOverrideConfig2.screenWidthDp = 150;
+ childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT;
+ child2.onOverrideConfigurationChanged(childOverrideConfig2);
+
+ // Check configuration on all levels when root override is updated.
+ rootOverrideConfig.smallestScreenWidthDp = 200;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig);
+ mergedOverrideConfig1.updateFrom(childOverrideConfig1);
+ final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1);
+
+ final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1);
+ mergedOverrideConfig2.updateFrom(childOverrideConfig2);
+ final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+
+ // Check configuration on all levels when root parent config is updated.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.screenHeightDp = 100;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration mergedRootConfig = new Configuration(rootParentConfig);
+ mergedRootConfig.updateFrom(rootOverrideConfig);
+
+ mergedConfig1.setTo(mergedRootConfig);
+ mergedConfig1.updateFrom(mergedOverrideConfig1);
+
+ mergedConfig2.setTo(mergedConfig1);
+ mergedConfig2.updateFrom(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(mergedRootConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 1259e0f..3df1df9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -58,7 +58,8 @@
public void setUp() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
- mWindowToken = new WindowToken(sWm, new Binder(), 0, false);
+ mWindowToken = new WindowToken(sWm, new Binder(), 0, false,
+ sWm.getDefaultDisplayContentLocked());
}
@Test
@@ -162,7 +163,6 @@
private WindowState createWindow(WindowState parent, int type) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
- return new WindowState(sWm, null, mIWindow, mWindowToken, parent, 0, 0, attrs, 0,
- sWm.getDefaultDisplayContentLocked(), 0);
+ return new WindowState(sWm, null, mIWindow, mWindowToken, parent, 0, 0, attrs, 0, 0);
}
}
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 3279886..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,15 +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,
- mWm.getDefaultDisplayContentLocked(), 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);
+ super(mWm, mock(IBinder.class), 0, false, mWm.getDefaultDisplayContentLocked());
}
int getWindowsCount() {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 220626a..af8c314 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -84,6 +84,14 @@
private static final String USB_CONFIG_PROPERTY = "sys.usb.config";
/**
+ * The property which stores the current build type (user/userdebug/eng).
+ */
+ private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
+
+ private static final String BUILD_TYPE_USERDEBUG = "userdebug";
+ private static final String BUILD_TYPE_ENG = "eng";
+
+ /**
* The non-persistent property which stores the current USB actual state.
*/
private static final String USB_STATE_PROPERTY = "sys.usb.state";
@@ -109,9 +117,8 @@
private static final int MSG_SYSTEM_READY = 3;
private static final int MSG_BOOT_COMPLETED = 4;
private static final int MSG_USER_SWITCHED = 5;
- private static final int MSG_SET_USB_DATA_UNLOCKED = 6;
- private static final int MSG_UPDATE_USER_RESTRICTIONS = 7;
- private static final int MSG_UPDATE_HOST_STATE = 8;
+ private static final int MSG_UPDATE_USER_RESTRICTIONS = 6;
+ private static final int MSG_UPDATE_HOST_STATE = 7;
private static final int AUDIO_MODE_SOURCE = 1;
@@ -291,7 +298,7 @@
if (functions != null) {
mAccessoryModeRequestTime = SystemClock.elapsedRealtime();
- setCurrentFunctions(functions);
+ setCurrentFunctions(functions, false);
}
}
@@ -340,14 +347,27 @@
// Restore default functions.
mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
UsbManager.USB_FUNCTION_NONE);
- if (UsbManager.USB_FUNCTION_NONE.equals(mCurrentFunctions)) {
- mCurrentFunctions = UsbManager.USB_FUNCTION_MTP;
- }
mCurrentFunctionsApplied = mCurrentFunctions.equals(
SystemProperties.get(USB_STATE_PROPERTY));
mAdbEnabled = UsbManager.containsFunction(getDefaultFunctions(),
UsbManager.USB_FUNCTION_ADB);
- setEnabledFunctions(null, false);
+
+ /**
+ * Remove MTP from persistent config, to bring usb to a good state
+ * after fixes to b/31814300. This block can be removed after the update
+ */
+ String persisted = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY);
+ if (UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_MTP)) {
+ SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY,
+ UsbManager.removeFunction(persisted, UsbManager.USB_FUNCTION_MTP));
+ }
+
+ String buildType = SystemProperties.get(BUILD_TYPE_PROPERTY);
+ if (buildType.equals(BUILD_TYPE_USERDEBUG) || buildType.equals(BUILD_TYPE_ENG)) {
+ setAdbEnabled(true);
+ }
+
+ setEnabledFunctions(null, false, false);
String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
updateState(state);
@@ -379,6 +399,14 @@
sendMessage(m);
}
+ public void sendMessage(int what, Object arg, boolean arg1) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg;
+ m.arg1 = (arg1 ? 1 : 0);
+ sendMessage(m);
+ }
+
public void updateState(String state) {
int connected, configured;
@@ -443,29 +471,24 @@
return waitForState(config);
}
- private void setUsbDataUnlocked(boolean enable) {
- if (DEBUG) Slog.d(TAG, "setUsbDataUnlocked: " + enable);
- mUsbDataUnlocked = enable;
- updateUsbNotification();
- updateUsbStateBroadcastIfNeeded();
- setEnabledFunctions(mCurrentFunctions, true);
- }
-
private void setAdbEnabled(boolean enable) {
if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
if (enable != mAdbEnabled) {
mAdbEnabled = enable;
+ String oldFunctions = mCurrentFunctions;
- // Due to the persist.sys.usb.config property trigger, changing adb state requires
- // persisting default function
- String oldFunctions = getDefaultFunctions();
- String newFunctions = applyAdbFunction(oldFunctions);
- if (!oldFunctions.equals(newFunctions)) {
- SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunctions);
+ // Persist the adb setting
+ String newFunction = applyAdbFunction(SystemProperties.get(
+ USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE));
+ SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunction);
+
+ // Remove mtp from the config if file transfer is not enabled
+ if (oldFunctions.equals(UsbManager.USB_FUNCTION_MTP) &&
+ !mUsbDataUnlocked && enable) {
+ oldFunctions = UsbManager.USB_FUNCTION_NONE;
}
- // After persisting them use the lock-down aware function set
- setEnabledFunctions(mCurrentFunctions, false);
+ setEnabledFunctions(oldFunctions, true, mUsbDataUnlocked);
updateAdbNotification();
}
@@ -477,10 +500,17 @@
/**
* Evaluates USB function policies and applies the change accordingly.
*/
- private void setEnabledFunctions(String functions, boolean forceRestart) {
+ private void setEnabledFunctions(String functions, boolean forceRestart,
+ boolean usbDataUnlocked) {
if (DEBUG) Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
+ "forceRestart=" + forceRestart);
+ if (usbDataUnlocked != mUsbDataUnlocked) {
+ mUsbDataUnlocked = usbDataUnlocked;
+ updateUsbNotification();
+ forceRestart = true;
+ }
+
// Try to set the enabled functions.
final String oldFunctions = mCurrentFunctions;
final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
@@ -517,7 +547,8 @@
}
private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
- if (functions == null) {
+ if (functions == null || applyAdbFunction(functions)
+ .equals(UsbManager.USB_FUNCTION_NONE)) {
functions = getDefaultFunctions();
}
functions = applyAdbFunction(functions);
@@ -583,7 +614,7 @@
// make sure accessory mode is off
// and restore default functions
Slog.d(TAG, "exited USB accessory mode");
- setEnabledFunctions(null, false);
+ setEnabledFunctions(null, false, false);
if (mCurrentAccessory != null) {
if (mBootCompleted) {
@@ -600,10 +631,6 @@
if (mBroadcastedIntent == null) {
for (String key : keySet) {
if (intent.getBooleanExtra(key, false)) {
- // MTP function is enabled by default.
- if (UsbManager.USB_FUNCTION_MTP.equals(key)) {
- continue;
- }
return true;
}
}
@@ -716,10 +743,7 @@
case MSG_UPDATE_STATE:
mConnected = (msg.arg1 == 1);
mConfigured = (msg.arg2 == 1);
- if (!mConnected) {
- // When a disconnect occurs, relock access to sensitive user data
- mUsbDataUnlocked = false;
- }
+
updateUsbNotification();
updateAdbNotification();
if (UsbManager.containsFunction(mCurrentFunctions,
@@ -727,7 +751,7 @@
updateCurrentAccessory();
} else if (!mConnected) {
// restore defaults when USB is disconnected
- setEnabledFunctions(null, false);
+ setEnabledFunctions(null, false, false);
}
if (mBootCompleted) {
updateUsbStateBroadcastIfNeeded();
@@ -750,13 +774,10 @@
break;
case MSG_SET_CURRENT_FUNCTIONS:
String functions = (String)msg.obj;
- setEnabledFunctions(functions, false);
+ setEnabledFunctions(functions, false, msg.arg1 == 1);
break;
case MSG_UPDATE_USER_RESTRICTIONS:
- setEnabledFunctions(mCurrentFunctions, false);
- break;
- case MSG_SET_USB_DATA_UNLOCKED:
- setUsbDataUnlocked(msg.arg1 == 1);
+ setEnabledFunctions(mCurrentFunctions, false, mUsbDataUnlocked);
break;
case MSG_SYSTEM_READY:
updateUsbNotification();
@@ -784,8 +805,7 @@
Slog.v(TAG, "Current user switched to " + mCurrentUser
+ "; resetting USB host stack for MTP or PTP");
// avoid leaking sensitive data from previous user
- mUsbDataUnlocked = false;
- setEnabledFunctions(mCurrentFunctions, true);
+ setEnabledFunctions(mCurrentFunctions, true, false);
}
mCurrentUser = msg.arg1;
}
@@ -969,14 +989,10 @@
return UsbManager.containsFunction(SystemProperties.get(USB_CONFIG_PROPERTY), function);
}
- public void setCurrentFunctions(String functions) {
- if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ")");
- mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
- }
-
- public void setUsbDataUnlocked(boolean unlocked) {
- if (DEBUG) Slog.d(TAG, "setUsbDataUnlocked(" + unlocked + ")");
- mHandler.sendMessage(MSG_SET_USB_DATA_UNLOCKED, unlocked);
+ public void setCurrentFunctions(String functions, boolean usbDataUnlocked) {
+ if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ", " +
+ usbDataUnlocked + ")");
+ mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked);
}
private void readOemUsbOverrideConfig() {
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index b789e17..af78c05 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -16,6 +16,7 @@
package com.android.server.usb;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index cc0fb8d..e03a14f 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -34,6 +34,7 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
+import android.os.AsyncTask;
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
@@ -42,6 +43,7 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FastXmlSerializer;
@@ -60,7 +62,9 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import libcore.io.IoUtils;
@@ -87,14 +91,24 @@
private final UserManager mUserManager;
private final @NonNull UsbSettingsManager mSettingsManager;
- // Maps DeviceFilter to user preferred application package
+ /** Maps DeviceFilter to user preferred application package */
+ @GuardedBy("mLock")
private final HashMap<DeviceFilter, UserPackage> mDevicePreferenceMap = new HashMap<>();
- // Maps AccessoryFilter to user preferred application package
+
+ /** Maps AccessoryFilter to user preferred application package */
+ @GuardedBy("mLock")
private final HashMap<AccessoryFilter, UserPackage> mAccessoryPreferenceMap = new HashMap<>();
private final Object mLock = new Object();
/**
+ * If a async task to persist the mDevicePreferenceMap and mAccessoryPreferenceMap is currently
+ * scheduled.
+ */
+ @GuardedBy("mLock")
+ private boolean mIsWriteSettingsScheduled;
+
+ /**
* A package of a user.
*/
@Immutable
@@ -591,6 +605,42 @@
});
}
+ /**
+ * Remove all defaults for a user.
+ *
+ * @param userToRemove The user the defaults belong to.
+ */
+ void removeAllDefaultsForUser(@NonNull UserHandle userToRemove) {
+ synchronized (mLock) {
+ boolean needToPersist = false;
+ Iterator<Map.Entry<DeviceFilter, UserPackage>> devicePreferenceIt = mDevicePreferenceMap
+ .entrySet().iterator();
+ while (devicePreferenceIt.hasNext()) {
+ Map.Entry<DeviceFilter, UserPackage> entry = devicePreferenceIt.next();
+
+ if (entry.getValue().user.equals(userToRemove)) {
+ devicePreferenceIt.remove();
+ needToPersist = true;
+ }
+ }
+
+ Iterator<Map.Entry<AccessoryFilter, UserPackage>> accessoryPreferenceIt =
+ mAccessoryPreferenceMap.entrySet().iterator();
+ while (accessoryPreferenceIt.hasNext()) {
+ Map.Entry<AccessoryFilter, UserPackage> entry = accessoryPreferenceIt.next();
+
+ if (entry.getValue().user.equals(userToRemove)) {
+ accessoryPreferenceIt.remove();
+ needToPersist = true;
+ }
+ }
+
+ if (needToPersist) {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ }
+
private void readPreference(XmlPullParser parser)
throws XmlPullParserException, IOException {
String packageName = null;
@@ -657,7 +707,7 @@
IoUtils.closeQuietly(fis);
}
- writeSettingsLocked();
+ scheduleWriteSettingsLocked();
// Success or failure, we delete single-user file
sSingleUserSettingsFile.delete();
@@ -695,48 +745,68 @@
}
}
- private void writeSettingsLocked() {
- if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
-
- FileOutputStream fos = null;
- try {
- fos = mSettingsFile.startWrite();
-
- FastXmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(fos, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startTag(null, "settings");
-
- for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
- serializer.startTag(null, "preference");
- serializer.attribute(null, "package", mDevicePreferenceMap.get(filter).packageName);
- serializer.attribute(null, "user",
- String.valueOf(getSerial(mDevicePreferenceMap.get(filter).user)));
- filter.write(serializer);
- serializer.endTag(null, "preference");
- }
-
- for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
- serializer.startTag(null, "preference");
- serializer.attribute(null, "package",
- mAccessoryPreferenceMap.get(filter).packageName);
- serializer.attribute(null, "user",
- String.valueOf(getSerial(mAccessoryPreferenceMap.get(filter).user)));
- filter.write(serializer);
- serializer.endTag(null, "preference");
- }
-
- serializer.endTag(null, "settings");
- serializer.endDocument();
-
- mSettingsFile.finishWrite(fos);
- } catch (IOException e) {
- Slog.e(TAG, "Failed to write settings", e);
- if (fos != null) {
- mSettingsFile.failWrite(fos);
- }
+ /**
+ * Schedule a async task to persist {@link #mDevicePreferenceMap} and
+ * {@link #mAccessoryPreferenceMap}. If a task is already scheduled but not completed, do
+ * nothing as the currently scheduled one will do the work.
+ * <p>Called with {@link #mLock} held.</p>
+ * <p>In the uncommon case that the system crashes in between the scheduling and the write the
+ * update is lost.</p>
+ */
+ private void scheduleWriteSettingsLocked() {
+ if (mIsWriteSettingsScheduled) {
+ return;
+ } else {
+ mIsWriteSettingsScheduled = true;
}
+
+ AsyncTask.execute(() -> {
+ synchronized (mLock) {
+ FileOutputStream fos = null;
+ try {
+ fos = mSettingsFile.startWrite();
+
+ FastXmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(fos, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+ true);
+ serializer.startTag(null, "settings");
+
+ for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
+ serializer.startTag(null, "preference");
+ serializer.attribute(null, "package",
+ mDevicePreferenceMap.get(filter).packageName);
+ serializer.attribute(null, "user",
+ String.valueOf(getSerial(mDevicePreferenceMap.get(filter).user)));
+ filter.write(serializer);
+ serializer.endTag(null, "preference");
+ }
+
+ for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
+ serializer.startTag(null, "preference");
+ serializer.attribute(null, "package",
+ mAccessoryPreferenceMap.get(filter).packageName);
+ serializer.attribute(null, "user", String.valueOf(
+ getSerial(mAccessoryPreferenceMap.get(filter).user)));
+ filter.write(serializer);
+ serializer.endTag(null, "preference");
+ }
+
+ serializer.endTag(null, "settings");
+ serializer.endDocument();
+
+ mSettingsFile.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write settings", e);
+ if (fos != null) {
+ mSettingsFile.failWrite(fos);
+ }
+ }
+
+ mIsWriteSettingsScheduled = false;
+ }
+ });
}
// Checks to see if a package matches a device or accessory.
@@ -1141,7 +1211,7 @@
}
if (changed) {
- writeSettingsLocked();
+ scheduleWriteSettingsLocked();
}
}
}
@@ -1178,7 +1248,7 @@
}
}
if (changed) {
- writeSettingsLocked();
+ scheduleWriteSettingsLocked();
}
}
}
@@ -1204,7 +1274,7 @@
}
}
if (changed) {
- writeSettingsLocked();
+ scheduleWriteSettingsLocked();
}
}
}
@@ -1237,7 +1307,7 @@
synchronized (mLock) {
if (clearPackageDefaultsLocked(userPackage)) {
- writeSettingsLocked();
+ scheduleWriteSettingsLocked();
}
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 02c7214..a87ac9e 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -16,6 +16,7 @@
package com.android.server.usb;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
@@ -83,7 +84,7 @@
@Override
public void onStopUser(int userHandle) {
- mUsbService.onStopUser(userHandle);
+ mUsbService.onStopUser(UserHandle.of(userHandle));
}
}
@@ -177,10 +178,10 @@
/**
* Execute operations when a user is stopped.
*
- * @param stoppedUserId The id of the used that is stopped
+ * @param stoppedUser The user that is stopped
*/
- private void onStopUser(@UserIdInt int stoppedUserId) {
- mSettingsManager.remove(stoppedUserId);
+ private void onStopUser(@NonNull UserHandle stoppedUser) {
+ mSettingsManager.remove(stoppedUser);
}
public void systemReady() {
@@ -371,7 +372,7 @@
}
@Override
- public void setCurrentFunction(String function) {
+ public void setCurrentFunction(String function, boolean usbDataUnlocked) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (!isSupportedCurrentFunction(function)) {
@@ -381,7 +382,7 @@
}
if (mDeviceManager != null) {
- mDeviceManager.setCurrentFunctions(function);
+ mDeviceManager.setCurrentFunctions(function, usbDataUnlocked);
} else {
throw new IllegalStateException("USB device mode not supported");
}
@@ -404,12 +405,6 @@
}
@Override
- public void setUsbDataUnlocked(boolean unlocked) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- mDeviceManager.setUsbDataUnlocked(unlocked);
- }
-
- @Override
public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
mDeviceManager.allowUsbDebugging(alwaysAllow, publicKey);
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index b251d26..24d5f09 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -108,11 +108,26 @@
/**
* Remove the settings for a user.
*
- * @param userIdToRemove The user o remove
+ * @param userToRemove The user to remove
*/
- void remove(@UserIdInt int userIdToRemove) {
+ void remove(@NonNull UserHandle userToRemove) {
synchronized (mSettingsByUser) {
- mSettingsByUser.remove(userIdToRemove);
+ mSettingsByUser.remove(userToRemove.getIdentifier());
+ }
+
+ synchronized (mSettingsByProfileGroup) {
+ if (mSettingsByProfileGroup.indexOfKey(userToRemove.getIdentifier()) >= 0) {
+ // The user to remove is the parent user of the group. The parent is the last user
+ // that gets removed. All state will be removed with the user
+ mSettingsByProfileGroup.remove(userToRemove.getIdentifier());
+ } else {
+ // We cannot find the parent user of the user that is removed, hence try to remove
+ // it from all profile groups.
+ int numProfileGroups = mSettingsByProfileGroup.size();
+ for (int i = 0; i < numProfileGroups; i++) {
+ mSettingsByProfileGroup.valueAt(i).removeAllDefaultsForUser(userToRemove);
+ }
+ }
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index 0dcd1521..dd7b5a8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -27,6 +27,9 @@
import android.text.TextUtils;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -40,7 +43,7 @@
static final boolean DBG = false;
private static final String NAME = "sound_model.db";
- private static final int VERSION = 5;
+ private static final int VERSION = 6;
public static interface SoundModelContract {
public static final String TABLE = "sound_model";
@@ -58,15 +61,19 @@
// Table Create Statement
private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE "
+ SoundModelContract.TABLE + "("
- + SoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
- + SoundModelContract.KEY_VENDOR_UUID + " TEXT, "
+ + SoundModelContract.KEY_MODEL_UUID + " TEXT,"
+ + SoundModelContract.KEY_VENDOR_UUID + " TEXT,"
+ SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER,"
+ SoundModelContract.KEY_TYPE + " INTEGER,"
+ SoundModelContract.KEY_DATA + " BLOB,"
+ SoundModelContract.KEY_RECOGNITION_MODES + " INTEGER,"
+ SoundModelContract.KEY_LOCALE + " TEXT,"
+ SoundModelContract.KEY_HINT_TEXT + " TEXT,"
- + SoundModelContract.KEY_USERS + " TEXT" + ")";
+ + SoundModelContract.KEY_USERS + " TEXT,"
+ + "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + ","
+ + SoundModelContract.KEY_LOCALE + ","
+ + SoundModelContract.KEY_USERS + ")"
+ + ")";
public DatabaseHelper(Context context) {
super(context, NAME, null, VERSION);
@@ -93,6 +100,44 @@
oldVersion++;
}
}
+ if (oldVersion == 5) {
+ // We need to enforce the new primary key constraint that the
+ // keyphrase id, locale, and users are unique. We have to first pull
+ // everything out of the database, remove duplicates, create the new
+ // table, then push everything back in.
+ String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE;
+ Cursor c = db.rawQuery(selectQuery, null);
+ List<SoundModelRecord> old_records = new ArrayList<SoundModelRecord>();
+ try {
+ if (c.moveToFirst()) {
+ do {
+ try {
+ old_records.add(new SoundModelRecord(5, c));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to extract V5 record", e);
+ }
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ }
+ db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE);
+ onCreate(db);
+ for (SoundModelRecord record : old_records) {
+ if (record.ifViolatesV6PrimaryKeyIsFirstOfAnyDuplicates(old_records)) {
+ try {
+ long return_value = record.writeToDatabase(6, db);
+ if (return_value == -1) {
+ Slog.e(TAG, "Database write failed " + record.modelUuid + ": "
+ + return_value);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to update V6 record " + record.modelUuid, e);
+ }
+ }
+ }
+ oldVersion++;
+ }
}
/**
@@ -279,4 +324,93 @@
}
return users;
}
+
+ private static class SoundModelRecord {
+ public final String modelUuid;
+ public final String vendorUuid;
+ public final int keyphraseId;
+ public final int type;
+ public final byte[] data;
+ public final int recognitionModes;
+ public final String locale;
+ public final String hintText;
+ public final String users;
+
+ public SoundModelRecord(int version, Cursor c) {
+ modelUuid = c.getString(c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
+ if (version >= 5) {
+ vendorUuid = c.getString(c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID));
+ } else {
+ vendorUuid = null;
+ }
+ keyphraseId = c.getInt(c.getColumnIndex(SoundModelContract.KEY_KEYPHRASE_ID));
+ type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
+ data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
+ recognitionModes = c.getInt(c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
+ locale = c.getString(c.getColumnIndex(SoundModelContract.KEY_LOCALE));
+ hintText = c.getString(c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+ users = c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS));
+ }
+
+ private boolean V6PrimaryKeyMatches(SoundModelRecord record) {
+ return keyphraseId == record.keyphraseId && stringComparisonHelper(locale, record.locale)
+ && stringComparisonHelper(users, record.users);
+ }
+
+ // Returns true if this record is a) the only record with the same V6 primary key, or b) the
+ // first record in the list of all records that have the same primary key and equal data.
+ // It will return false if a) there are any records that have the same primary key and
+ // different data, or b) there is a previous record in the list that has the same primary
+ // key and data.
+ // Note that 'this' object must be inside the list.
+ public boolean ifViolatesV6PrimaryKeyIsFirstOfAnyDuplicates(
+ List<SoundModelRecord> records) {
+ // First pass - check to see if all the records that have the same primary key have
+ // duplicated data.
+ for (SoundModelRecord record : records) {
+ if (this == record) {
+ continue;
+ }
+ // If we have different/missing data with the same primary key, then we should drop
+ // everything.
+ if (this.V6PrimaryKeyMatches(record) && !Arrays.equals(data, record.data)) {
+ return false;
+ }
+ }
+
+ // We only want to return true for the first duplicated model.
+ for (SoundModelRecord record : records) {
+ if (this.V6PrimaryKeyMatches(record)) {
+ return this == record;
+ }
+ }
+ return true;
+ }
+
+ public long writeToDatabase(int version, SQLiteDatabase db) {
+ ContentValues values = new ContentValues();
+ values.put(SoundModelContract.KEY_MODEL_UUID, modelUuid);
+ if (version >= 5) {
+ values.put(SoundModelContract.KEY_VENDOR_UUID, vendorUuid);
+ }
+ values.put(SoundModelContract.KEY_KEYPHRASE_ID, keyphraseId);
+ values.put(SoundModelContract.KEY_TYPE, type);
+ values.put(SoundModelContract.KEY_DATA, data);
+ values.put(SoundModelContract.KEY_RECOGNITION_MODES, recognitionModes);
+ values.put(SoundModelContract.KEY_LOCALE, locale);
+ values.put(SoundModelContract.KEY_HINT_TEXT, hintText);
+ values.put(SoundModelContract.KEY_USERS, users);
+
+ return db.insertWithOnConflict(
+ SoundModelContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ // Helper for checking string equality - including the case when they are null.
+ static private boolean stringComparisonHelper(String a, String b) {
+ if (a != null) {
+ return a.equals(b);
+ }
+ return a == b;
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a04034e..e8976a7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1100,7 +1100,7 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump PowerManager from from pid="
+ pw.println("Permission Denial: can't dump voiceinteraction from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index c3075b3..8268b40 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -254,8 +254,7 @@
&& structureEnabled) {
mAssistData.clear();
final int count = activityToken != null ? 1 : topActivities.size();
- // Temp workaround for bug: 28348867 Revert after DP3
- for (int i = 0; i < count && i < 1; i++) {
+ for (int i = 0; i < count; i++) {
IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
try {
MetricsLogger.count(mContext, "assist_with_context", 1);
@@ -570,7 +569,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 a965342..1e5769f 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -16,11 +16,18 @@
package android.telecom;
+import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
+import android.telecom.Logging.EventManager;
+import android.telecom.Logging.Session;
+import android.telecom.Logging.SessionManager;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.IllegalFormatException;
@@ -31,22 +38,285 @@
*
* @hide
*/
-final public class Log {
+public class Log {
- // Generic tag for all Telecom Framework logging
- private static final String TAG = "TelecomFramework";
+ private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes
- public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
+ private static final int EVENTS_TO_CACHE = 10;
+ private static final int EVENTS_TO_CACHE_DEBUG = 20;
+
+ // Generic tag for all Telecom logging
+ @VisibleForTesting
+ public static String TAG = "TelecomFramework";
+
+ private static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG);
public static final boolean INFO = isLoggable(android.util.Log.INFO);
public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE);
public static final boolean WARN = isLoggable(android.util.Log.WARN);
public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
- private static MessageDigest sMessageDigest;
- private static final Object sMessageDigestLock = new Object();
+ // Used to synchronize singleton logging lazy initialization
+ private static final Object sSingletonSync = new Object();
+ private static EventManager sEventManager;
+ private static SessionManager sSessionManager;
- private Log() {}
+ /**
+ * Tracks whether user-activated extended logging is enabled.
+ */
+ private static boolean sIsUserExtendedLoggingEnabled = false;
+
+ /**
+ * The time when user-activated extended logging should be ended. Used to determine when
+ * extended logging should automatically be disabled.
+ */
+ private static long sUserExtendedLoggingStopTime = 0;
+
+ private Log() {
+ }
+
+ public static void d(String prefix, String format, Object... args) {
+ if (sIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+ } else if (DEBUG) {
+ android.util.Slog.d(TAG, buildMessage(prefix, format, args));
+ }
+ }
+
+ public static void d(Object objectPrefix, String format, Object... args) {
+ if (sIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ } else if (DEBUG) {
+ android.util.Slog.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ }
+ }
+
+ public static void i(String prefix, String format, Object... args) {
+ if (INFO) {
+ android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+ }
+ }
+
+ public static void i(Object objectPrefix, String format, Object... args) {
+ if (INFO) {
+ android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ }
+ }
+
+ public static void v(String prefix, String format, Object... args) {
+ if (sIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+ } else if (VERBOSE) {
+ android.util.Slog.v(TAG, buildMessage(prefix, format, args));
+ }
+ }
+
+ public static void v(Object objectPrefix, String format, Object... args) {
+ if (sIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ } else if (VERBOSE) {
+ android.util.Slog.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ }
+ }
+
+ public static void w(String prefix, String format, Object... args) {
+ if (WARN) {
+ android.util.Slog.w(TAG, buildMessage(prefix, format, args));
+ }
+ }
+
+ public static void w(Object objectPrefix, String format, Object... args) {
+ if (WARN) {
+ android.util.Slog.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ }
+ }
+
+ public static void e(String prefix, Throwable tr, String format, Object... args) {
+ if (ERROR) {
+ android.util.Slog.e(TAG, buildMessage(prefix, format, args), tr);
+ }
+ }
+
+ public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
+ if (ERROR) {
+ android.util.Slog.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+ tr);
+ }
+ }
+
+ public static void wtf(String prefix, Throwable tr, String format, Object... args) {
+ android.util.Slog.wtf(TAG, buildMessage(prefix, format, args), tr);
+ }
+
+ public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
+ android.util.Slog.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+ tr);
+ }
+
+ public static void wtf(String prefix, String format, Object... args) {
+ String msg = buildMessage(prefix, format, args);
+ android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
+ }
+
+ public static void wtf(Object objectPrefix, String format, Object... args) {
+ String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
+ android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
+ }
+
+ /**
+ * The ease of use methods below only act mostly as proxies to the Session and Event Loggers.
+ * They also control the lazy loaders of the singleton instances, which will never be loaded if
+ * the proxy methods aren't used.
+ *
+ * Please see each method's documentation inside of their respective implementations in the
+ * loggers.
+ */
+
+ public static void setSessionContext(Context context) {
+ getSessionManager().setContext(context);
+ }
+
+ public static void startSession(String shortMethodName) {
+ getSessionManager().startSession(shortMethodName, null);
+ }
+
+ public static void startSession(String shortMethodName, String callerIdentification) {
+ getSessionManager().startSession(shortMethodName, callerIdentification);
+ }
+
+ public static void startSession(Session.Info info, String shortMethodName,
+ String callerIdentification) {
+ getSessionManager().startSession(info, shortMethodName, callerIdentification);
+ }
+
+ public static void startExternalSession(Session.Info sessionInfo, String shortMethodName) {
+ getSessionManager().startExternalSession(sessionInfo, shortMethodName);
+ }
+
+ public static Session createSubsession() {
+ return getSessionManager().createSubsession();
+ }
+
+ public static void cancelSubsession(Session subsession) {
+ getSessionManager().cancelSubsession(subsession);
+ }
+
+ public static void continueSession(Session subsession, String shortMethodName) {
+ getSessionManager().continueSession(subsession, shortMethodName);
+ }
+
+ public static void endSession() {
+ 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!
+ synchronized (sSingletonSync) {
+ if (sSessionManager != null) {
+ return getSessionManager().getSessionId();
+ } else {
+ return "";
+ }
+ }
+ }
+
+ public static void addEvent(EventManager.Loggable recordEntry, String event) {
+ getEventManager().event(recordEntry, event, null);
+ }
+
+ public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) {
+ getEventManager().event(recordEntry, event, data);
+ }
+
+ public static void addEvent(EventManager.Loggable recordEntry, String event, String format,
+ Object... args) {
+ getEventManager().event(recordEntry, event, format, args);
+ }
+
+ public static void registerEventListener(EventManager.EventListener e) {
+ getEventManager().registerEventListener(e);
+ }
+
+ public static void addRequestResponsePair(EventManager.TimedEventPair p) {
+ getEventManager().addRequestResponsePair(p);
+ }
+
+ public static void dumpEvents(IndentingPrintWriter pw) {
+ // If the Events logger has not been initialized, then there have been no events logged.
+ // Don't load it now!
+ synchronized (sSingletonSync) {
+ if (sEventManager != null) {
+ getEventManager().dumpEvents(pw);
+ } else {
+ pw.println("No Historical Events Logged.");
+ }
+ }
+ }
+
+ /**
+ * Enable or disable extended telecom logging.
+ *
+ * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled,
+ * {@code false} if it should be disabled.
+ */
+ public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) {
+ // If the state hasn't changed, bail early.
+ if (sIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) {
+ return;
+ }
+
+ if (sEventManager != null) {
+ sEventManager.changeEventCacheSize(isExtendedLoggingEnabled ?
+ EVENTS_TO_CACHE_DEBUG : EVENTS_TO_CACHE);
+ }
+
+ sIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled;
+ if (sIsUserExtendedLoggingEnabled) {
+ sUserExtendedLoggingStopTime = System.currentTimeMillis()
+ + EXTENDED_LOGGING_DURATION_MILLIS;
+ } else {
+ sUserExtendedLoggingStopTime = 0;
+ }
+ }
+
+ private static EventManager getEventManager() {
+ // Checking for null again outside of synchronization because we only need to synchronize
+ // during the lazy loading of the events logger. We don't need to synchronize elsewhere.
+ if (sEventManager == null) {
+ synchronized (sSingletonSync) {
+ if (sEventManager == null) {
+ sEventManager = new EventManager(Log::getSessionId);
+ return sEventManager;
+ }
+ }
+ }
+ return sEventManager;
+ }
+
+ private static SessionManager getSessionManager() {
+ // Checking for null again outside of synchronization because we only need to synchronize
+ // during the lazy loading of the session logger. We don't need to synchronize elsewhere.
+ if (sSessionManager == null) {
+ synchronized (sSingletonSync) {
+ if (sSessionManager == null) {
+ sSessionManager = new SessionManager();
+ return sSessionManager;
+ }
+ }
+ }
+ return sSessionManager;
+ }
+
+ private static MessageDigest sMessageDigest;
public static void initMd5Sum() {
new AsyncTask<Void, Void, Void>() {
@@ -58,96 +328,69 @@
} catch (NoSuchAlgorithmException e) {
md = null;
}
- synchronized (sMessageDigestLock) {
- sMessageDigest = md;
- }
+ sMessageDigest = md;
return null;
}
}.execute();
}
+ public static void setTag(String tag) {
+ TAG = tag;
+ }
+
+ /**
+ * If user enabled extended logging is enabled and the time limit has passed, disables the
+ * extended logging.
+ */
+ private static void maybeDisableLogging() {
+ if (!sIsUserExtendedLoggingEnabled) {
+ return;
+ }
+
+ if (sUserExtendedLoggingStopTime < System.currentTimeMillis()) {
+ sUserExtendedLoggingStopTime = 0;
+ sIsUserExtendedLoggingEnabled = false;
+ }
+ }
+
public static boolean isLoggable(int level) {
return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
}
- public static void d(String prefix, String format, Object... args) {
- if (DEBUG) {
- android.util.Log.d(TAG, buildMessage(prefix, format, args));
+ public static String piiHandle(Object pii) {
+ if (pii == null || VERBOSE) {
+ return String.valueOf(pii);
}
- }
- public static void d(Object objectPrefix, String format, Object... args) {
- if (DEBUG) {
- android.util.Log.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ StringBuilder sb = new StringBuilder();
+ if (pii instanceof Uri) {
+ Uri uri = (Uri) pii;
+ String scheme = uri.getScheme();
+
+ if (!TextUtils.isEmpty(scheme)) {
+ sb.append(scheme).append(":");
+ }
+
+ String textToObfuscate = uri.getSchemeSpecificPart();
+ if (PhoneAccount.SCHEME_TEL.equals(scheme)) {
+ for (int i = 0; i < textToObfuscate.length(); i++) {
+ char c = textToObfuscate.charAt(i);
+ sb.append(PhoneNumberUtils.isDialable(c) ? "*" : c);
+ }
+ } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
+ for (int i = 0; i < textToObfuscate.length(); i++) {
+ char c = textToObfuscate.charAt(i);
+ if (c != '@' && c != '.') {
+ c = '*';
+ }
+ sb.append(c);
+ }
+ } else {
+ sb.append(pii(pii));
+ }
}
- }
- public static void i(String prefix, String format, Object... args) {
- if (INFO) {
- android.util.Log.i(TAG, buildMessage(prefix, format, args));
- }
- }
-
- public static void i(Object objectPrefix, String format, Object... args) {
- if (INFO) {
- android.util.Log.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
- }
- }
-
- public static void v(String prefix, String format, Object... args) {
- if (VERBOSE) {
- android.util.Log.v(TAG, buildMessage(prefix, format, args));
- }
- }
-
- public static void v(Object objectPrefix, String format, Object... args) {
- if (VERBOSE) {
- android.util.Log.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
- }
- }
-
- public static void w(String prefix, String format, Object... args) {
- if (WARN) {
- android.util.Log.w(TAG, buildMessage(prefix, format, args));
- }
- }
-
- public static void w(Object objectPrefix, String format, Object... args) {
- if (WARN) {
- android.util.Log.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
- }
- }
-
- public static void e(String prefix, Throwable tr, String format, Object... args) {
- if (ERROR) {
- android.util.Log.e(TAG, buildMessage(prefix, format, args), tr);
- }
- }
-
- public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
- if (ERROR) {
- android.util.Log.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
- tr);
- }
- }
-
- public static void wtf(String prefix, Throwable tr, String format, Object... args) {
- android.util.Log.wtf(TAG, buildMessage(prefix, format, args), tr);
- }
-
- public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
- android.util.Log.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
- tr);
- }
-
- public static void wtf(String prefix, String format, Object... args) {
- String msg = buildMessage(prefix, format, args);
- android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
- }
-
- public static void wtf(Object objectPrefix, String format, Object... args) {
- String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
- android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
+ return sb.toString();
}
/**
@@ -158,47 +401,18 @@
public static String pii(Object pii) {
if (pii == null || VERBOSE) {
return String.valueOf(pii);
- } if (pii instanceof Uri) {
- return piiUri((Uri) pii);
}
return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
}
- private static String piiUri(Uri handle) {
- StringBuilder sb = new StringBuilder();
- String scheme = handle.getScheme();
- if (!TextUtils.isEmpty(scheme)) {
- sb.append(scheme).append(":");
- }
- String value = handle.getSchemeSpecificPart();
- if (!TextUtils.isEmpty(value)) {
- for (int i = 0; i < value.length(); i++) {
- char c = value.charAt(i);
- if (PhoneNumberUtils.isStartsPostDial(c)) {
- sb.append(c);
- } else if (PhoneNumberUtils.isDialable(c)) {
- sb.append("*");
- } else if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) {
- sb.append("*");
- } else {
- sb.append(c);
- }
- }
- }
- return sb.toString();
-
- }
-
private static String secureHash(byte[] input) {
- synchronized (sMessageDigestLock) {
- if (sMessageDigest != null) {
- sMessageDigest.reset();
- sMessageDigest.update(input);
- byte[] result = sMessageDigest.digest();
- return encodeHex(result);
- } else {
- return "Uninitialized SHA1";
- }
+ if (sMessageDigest != null) {
+ sMessageDigest.reset();
+ sMessageDigest.update(input);
+ byte[] result = sMessageDigest.digest();
+ return encodeHex(result);
+ } else {
+ return "Uninitialized SHA1";
}
}
@@ -221,15 +435,19 @@
}
private static String buildMessage(String prefix, String format, Object... args) {
+ // Incorporate thread ID and calling method into prefix
+ String sessionName = getSessionId();
+ String sessionPostfix = TextUtils.isEmpty(sessionName) ? "" : ": " + sessionName;
+
String msg;
try {
msg = (args == null || args.length == 0) ? format
: String.format(Locale.US, format, args);
} catch (IllegalFormatException ife) {
- wtf("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.)";
}
- return String.format(Locale.US, "%s: %s", prefix, msg);
+ return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
}
}
diff --git a/telecomm/java/android/telecom/Logging/EventManager.java b/telecomm/java/android/telecom/Logging/EventManager.java
new file mode 100644
index 0000000..2cd1b96
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/EventManager.java
@@ -0,0 +1,375 @@
+/*
+ * 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.annotation.NonNull;
+import android.telecom.Log;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.IllegalFormatException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A utility class that provides the ability to define Events that a subsystem deems important, and
+ * then relate those events to other events so that information can be extracted. For example, a
+ * START and FINISH event can be defined and when a START and then FINISH occurs in a sequence, the
+ * time it took to complete that sequence can be saved to be retrieved later.
+ * @hide
+ */
+
+public class EventManager {
+
+ public static final String TAG = "Logging.Events";
+ @VisibleForTesting
+ public static final int DEFAULT_EVENTS_TO_CACHE = 10; // Arbitrarily chosen.
+
+ public interface Loggable {
+ /**
+ * @return a unique String ID that will allow the Event to be recognized later in the logs.
+ */
+ String getId();
+
+ /**
+ * @return Formatted information about the state that will be printed out later in the logs.
+ */
+ String getDescription();
+ }
+
+ private final Map<Loggable, EventRecord> mCallEventRecordMap = new HashMap<>();
+ private LinkedBlockingQueue<EventRecord> mEventRecords =
+ new LinkedBlockingQueue<>(DEFAULT_EVENTS_TO_CACHE);
+
+ private List<EventListener> mEventListeners = new ArrayList<>();
+
+ public interface EventListener {
+ /**
+ * Notifies the implementation of this method that a new event record has been added.
+ * @param eventRecord Reference to the recently added EventRecord
+ */
+ void eventRecordAdded(EventRecord eventRecord);
+ }
+
+ private SessionManager.ISessionIdQueryHandler mSessionIdHandler;
+ /**
+ * Maps from request events to a list of possible response events. Used to track
+ * end-to-end timing for critical user-facing operations in Telecom.
+ */
+ private final Map<String, List<TimedEventPair>> requestResponsePairs = new HashMap<>();
+
+ private static final Object mSync = new Object();
+
+ /**
+ * Stores the various events.
+ * Also stores all request-response pairs amongst the events.
+ */
+ public static class TimedEventPair {
+ private static final long DEFAULT_TIMEOUT = 3000L;
+
+ String mRequest;
+ String mResponse;
+ String mName;
+ long mTimeoutMillis = DEFAULT_TIMEOUT;
+
+ public TimedEventPair(String request, String response, String name) {
+ this.mRequest = request;
+ this.mResponse = response;
+ this.mName = name;
+ }
+
+ public TimedEventPair(String request, String response, String name, long timeoutMillis) {
+ this.mRequest = request;
+ this.mResponse = response;
+ this.mName = name;
+ this.mTimeoutMillis = timeoutMillis;
+ }
+ }
+
+ public void addRequestResponsePair(TimedEventPair p) {
+ if (requestResponsePairs.containsKey(p.mRequest)) {
+ requestResponsePairs.get(p.mRequest).add(p);
+ } else {
+ ArrayList<TimedEventPair> responses = new ArrayList<>();
+ responses.add(p);
+ requestResponsePairs.put(p.mRequest, responses);
+ }
+ }
+
+ public static class Event {
+ public String eventId;
+ public String sessionId;
+ public long time;
+ public Object data;
+
+ public Event(String eventId, String sessionId, long time, Object data) {
+ this.eventId = eventId;
+ this.sessionId = sessionId;
+ this.time = time;
+ this.data = data;
+ }
+ }
+
+ public class EventRecord {
+ public class EventTiming extends TimedEvent<String> {
+ public String name;
+ public long time;
+
+ public EventTiming(String name, long time) {
+ this.name = name;
+ this.time = time;
+ }
+
+ public String getKey() {
+ return name;
+ }
+
+ public long getTime() {
+ return time;
+ }
+ }
+
+ private class PendingResponse {
+ String requestEventId;
+ long requestEventTimeMillis;
+ long timeoutMillis;
+ String name;
+
+ public PendingResponse(String requestEventId, long requestEventTimeMillis,
+ long timeoutMillis, String name) {
+ this.requestEventId = requestEventId;
+ this.requestEventTimeMillis = requestEventTimeMillis;
+ this.timeoutMillis = timeoutMillis;
+ this.name = name;
+ }
+ }
+
+ private final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
+ private final List<Event> mEvents = new LinkedList<>();
+ private final Loggable mRecordEntry;
+
+ public EventRecord(Loggable recordEntry) {
+ mRecordEntry = recordEntry;
+ }
+
+ public Loggable getRecordEntry() {
+ return mRecordEntry;
+ }
+
+ public void addEvent(String event, String sessionId, Object data) {
+ mEvents.add(new Event(event, sessionId, System.currentTimeMillis(), data));
+ Log.i("Event", "RecordEntry %s: %s, %s", mRecordEntry.getId(), event, data);
+ }
+
+ public List<Event> getEvents() {
+ return mEvents;
+ }
+
+ public List<EventTiming> extractEventTimings() {
+ if (mEvents == null) {
+ return Collections.emptyList();
+ }
+
+ LinkedList<EventTiming> result = new LinkedList<>();
+ Map<String, PendingResponse> pendingResponses = new HashMap<>();
+ for (Event event : mEvents) {
+ if (requestResponsePairs.containsKey(event.eventId)) {
+ // This event expects a response, so add that expected response to the maps
+ // of pending events.
+ for (EventManager.TimedEventPair p : requestResponsePairs.get(event.eventId)) {
+ pendingResponses.put(p.mResponse, new PendingResponse(event.eventId,
+ event.time, p.mTimeoutMillis, p.mName));
+ }
+ }
+
+ PendingResponse pendingResponse = pendingResponses.remove(event.eventId);
+ if (pendingResponse != null) {
+ long elapsedTime = event.time - pendingResponse.requestEventTimeMillis;
+ if (elapsedTime < pendingResponse.timeoutMillis) {
+ result.add(new EventTiming(pendingResponse.name, elapsedTime));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.print(mRecordEntry.getDescription());
+
+ pw.increaseIndent();
+ for (Event event : mEvents) {
+ pw.print(sDateFormat.format(new Date(event.time)));
+ pw.print(" - ");
+ pw.print(event.eventId);
+ if (event.data != null) {
+ pw.print(" (");
+ Object data = event.data;
+
+ if (data instanceof Loggable) {
+ // If the data is another Loggable, then change the data to the
+ // Entry's Event ID instead.
+ EventRecord record = mCallEventRecordMap.get(data);
+ if (record != null) {
+ data = "RecordEntry " + record.mRecordEntry.getId();
+ }
+ }
+
+ pw.print(data);
+ pw.print(")");
+ }
+ if (!TextUtils.isEmpty(event.sessionId)) {
+ pw.print(":");
+ pw.print(event.sessionId);
+ }
+ pw.println();
+ }
+
+ pw.println("Timings (average for this call, milliseconds):");
+ pw.increaseIndent();
+ Map<String, Double> avgEventTimings = EventTiming.averageTimings(extractEventTimings());
+ List<String> eventNames = new ArrayList<>(avgEventTimings.keySet());
+ Collections.sort(eventNames);
+ for (String eventName : eventNames) {
+ pw.printf("%s: %.2f\n", eventName, avgEventTimings.get(eventName));
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ }
+
+ public EventManager(@NonNull SessionManager.ISessionIdQueryHandler l) {
+ mSessionIdHandler = l;
+ }
+
+ public void event(Loggable recordEntry, String event, Object data) {
+ String currentSessionID = mSessionIdHandler.getSessionId();
+
+ if (recordEntry == null) {
+ Log.i(TAG, "Non-call EVENT: %s, %s", event, data);
+ return;
+ }
+ synchronized (mEventRecords) {
+ if (!mCallEventRecordMap.containsKey(recordEntry)) {
+ EventRecord newRecord = new EventRecord(recordEntry);
+ addEventRecord(newRecord);
+ }
+
+ EventRecord record = mCallEventRecordMap.get(recordEntry);
+ record.addEvent(event, currentSessionID, data);
+ }
+ }
+
+ public void event(Loggable recordEntry, String event, String format, Object... args) {
+ String msg;
+ try {
+ msg = (args == null || args.length == 0) ? format
+ : String.format(Locale.US, format, args);
+ } catch (IllegalFormatException ife) {
+ Log.e(this, ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+ args.length);
+ msg = format + " (An error occurred while formatting the message.)";
+ }
+
+ event(recordEntry, event, msg);
+ }
+
+ public void dumpEvents(IndentingPrintWriter pw) {
+ pw.println("Historical Events:");
+ pw.increaseIndent();
+ for (EventRecord eventRecord : mEventRecords) {
+ eventRecord.dump(pw);
+ }
+ pw.decreaseIndent();
+ }
+
+ public void changeEventCacheSize(int newSize) {
+
+ // Resize the event queue.
+ LinkedBlockingQueue<EventRecord> oldEventLog = mEventRecords;
+ mEventRecords = new LinkedBlockingQueue<>(newSize);
+ mCallEventRecordMap.clear();
+
+ oldEventLog.forEach((newRecord -> {
+ Loggable recordEntry = newRecord.getRecordEntry();
+ // Copy the existing queue into the new one.
+ // First remove the oldest entry if no new ones exist.
+ if (mEventRecords.remainingCapacity() == 0) {
+ EventRecord record = mEventRecords.poll();
+ if (record != null) {
+ mCallEventRecordMap.remove(record.getRecordEntry());
+ }
+ }
+
+ // Now add a new entry
+ mEventRecords.add(newRecord);
+ mCallEventRecordMap.put(recordEntry, newRecord);
+
+ // Don't worry about notifying mEventListeners, since we are just resizing the records.
+ }));
+ }
+
+ public void registerEventListener(EventListener e) {
+ if (e != null) {
+ synchronized (mSync) {
+ mEventListeners.add(e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public LinkedBlockingQueue<EventRecord> getEventRecords() {
+ return mEventRecords;
+ }
+
+ @VisibleForTesting
+ public Map<Loggable, EventRecord> getCallEventRecordMap() {
+ return mCallEventRecordMap;
+ }
+
+ private void addEventRecord(EventRecord newRecord) {
+ Loggable recordEntry = newRecord.getRecordEntry();
+
+ // First remove the oldest entry if no new ones exist.
+ if (mEventRecords.remainingCapacity() == 0) {
+ EventRecord record = mEventRecords.poll();
+ if (record != null) {
+ mCallEventRecordMap.remove(record.getRecordEntry());
+ }
+ }
+
+ // Now add a new entry
+ mEventRecords.add(newRecord);
+ mCallEventRecordMap.put(recordEntry, newRecord);
+ 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/Session.java b/telecomm/java/android/telecom/Logging/Session.java
new file mode 100644
index 0000000..093c0f9
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -0,0 +1,354 @@
+/*
+ * 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.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * The session that stores information about a thread's point of entry into the Telecom code that
+ * persists until the thread exits Telecom.
+ * @hide
+ */
+public class Session {
+
+ public static final String START_SESSION = "START_SESSION";
+ public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
+ public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
+ public static final String END_SUBSESSION = "END_SUBSESSION";
+ public static final String END_SESSION = "END_SESSION";
+
+ public static final String SUBSESSION_SEPARATION_CHAR = "->";
+ public static final String EXTERNAL_INDICATOR = "E-";
+
+ /**
+ * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
+ * if the Session is canceled.
+ */
+ public static final int UNDEFINED = -1;
+
+ public static class Info implements Parcelable {
+ public final String sessionId;
+ public final String shortMethodName;
+
+ private Info(String id, String methodName) {
+ sessionId = id;
+ shortMethodName = methodName;
+ }
+
+ public static Info getInfo (Session s) {
+ return new Info(s.getFullSessionId(), s.getShortMethodName());
+ }
+
+ /** Responsible for creating Info objects for deserialized Parcels. */
+ public static final Parcelable.Creator<Info> CREATOR =
+ new Parcelable.Creator<Info> () {
+ @Override
+ public Info createFromParcel(Parcel source) {
+ String id = source.readString();
+ String methodName = source.readString();
+ return new Info(id, methodName);
+ }
+
+ @Override
+ public Info[] newArray(int size) {
+ return new Info[size];
+ }
+ };
+
+ /** {@inheritDoc} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Writes Info object into a Parcel. */
+ @Override
+ public void writeToParcel(Parcel destination, int flags) {
+ destination.writeString(sessionId);
+ destination.writeString(shortMethodName);
+ }
+ }
+
+ private String mSessionId;
+ private String mShortMethodName;
+ private long mExecutionStartTimeMs;
+ private long mExecutionEndTimeMs = UNDEFINED;
+ private Session mParentSession;
+ private ArrayList<Session> mChildSessions;
+ private boolean mIsCompleted = false;
+ private boolean mIsExternal = false;
+ private int mChildCounter = 0;
+ // True if this is a subsession that has been started from the same thread as the parent
+ // session. This can happen if Log.startSession(...) is called multiple times on the same
+ // thread in the case of one Telecom entry point method calling another entry point method.
+ // In this case, we can just make this subsession "invisible," but still keep track of it so
+ // that the Log.endSession() calls match up.
+ private boolean mIsStartedFromActiveSession = false;
+ // Optionally provided info about the method/class/component that started the session in order
+ // to make Logging easier. This info will be provided in parentheses along with the session.
+ private String mOwnerInfo;
+ // Cache Full Method path so that recursive population of the full method path only needs to
+ // be calculated once.
+ private String mFullMethodPathCache;
+
+ public Session(String sessionId, String shortMethodName, long startTimeMs,
+ boolean isStartedFromActiveSession, String ownerInfo) {
+ setSessionId(sessionId);
+ setShortMethodName(shortMethodName);
+ mExecutionStartTimeMs = startTimeMs;
+ mParentSession = null;
+ mChildSessions = new ArrayList<>(5);
+ mIsStartedFromActiveSession = isStartedFromActiveSession;
+ mOwnerInfo = ownerInfo;
+ }
+
+ public void setSessionId(@NonNull String sessionId) {
+ if (sessionId == null) {
+ mSessionId = "?";
+ }
+ mSessionId = sessionId;
+ }
+
+ public String getShortMethodName() {
+ return mShortMethodName;
+ }
+
+ public void setShortMethodName(String shortMethodName) {
+ if (shortMethodName == null) {
+ shortMethodName = "";
+ }
+ mShortMethodName = shortMethodName;
+ }
+
+ public void setIsExternal(boolean isExternal) {
+ mIsExternal = isExternal;
+ }
+
+ public boolean isExternal() {
+ return mIsExternal;
+ }
+
+ public void setParentSession(Session parentSession) {
+ mParentSession = parentSession;
+ }
+
+ public void addChild(Session childSession) {
+ if (childSession != null) {
+ mChildSessions.add(childSession);
+ }
+ }
+
+ public void removeChild(Session child) {
+ if (child != null) {
+ mChildSessions.remove(child);
+ }
+ }
+
+ public long getExecutionStartTimeMilliseconds() {
+ return mExecutionStartTimeMs;
+ }
+
+ public void setExecutionStartTimeMs(long startTimeMs) {
+ mExecutionStartTimeMs = startTimeMs;
+ }
+
+ public Session getParentSession() {
+ return mParentSession;
+ }
+
+ public ArrayList<Session> getChildSessions() {
+ return mChildSessions;
+ }
+
+ public boolean isSessionCompleted() {
+ return mIsCompleted;
+ }
+
+ public boolean isStartedFromActiveSession() {
+ return mIsStartedFromActiveSession;
+ }
+
+ public Info getInfo() {
+ return Info.getInfo(this);
+ }
+
+ @VisibleForTesting
+ public String getSessionId() {
+ return mSessionId;
+ }
+
+ // Mark this session complete. This will be deleted by Log when all subsessions are complete
+ // as well.
+ public void markSessionCompleted(long executionEndTimeMs) {
+ mExecutionEndTimeMs = executionEndTimeMs;
+ mIsCompleted = true;
+ }
+
+ public long getLocalExecutionTime() {
+ if (mExecutionEndTimeMs == UNDEFINED) {
+ return UNDEFINED;
+ }
+ return mExecutionEndTimeMs - mExecutionStartTimeMs;
+ }
+
+ public synchronized String getNextChildId() {
+ return String.valueOf(mChildCounter++);
+ }
+
+ // Builds full session id recursively
+ private String getFullSessionId() {
+ // Cache mParentSession locally to prevent a concurrency problem where
+ // Log.endParentSessions() is called while a logging statement is running (Log.i, for
+ // example) and setting mParentSession to null in a different thread after the null check
+ // occurred.
+ Session parentSession = mParentSession;
+ if (parentSession == null) {
+ return mSessionId;
+ } else {
+ return parentSession.getFullSessionId() + "_" + mSessionId;
+ }
+ }
+
+ // Print out the full Session tree from any subsession node
+ public String printFullSessionTree() {
+ // Get to the top of the tree
+ Session topNode = this;
+ while (topNode.getParentSession() != null) {
+ topNode = topNode.getParentSession();
+ }
+ return topNode.printSessionTree();
+ }
+
+ // Recursively move down session tree using DFS, but print out each node when it is reached.
+ public String printSessionTree() {
+ StringBuilder sb = new StringBuilder();
+ printSessionTree(0, sb);
+ return sb.toString();
+ }
+
+ private void printSessionTree(int tabI, StringBuilder sb) {
+ sb.append(toString());
+ for (Session child : mChildSessions) {
+ sb.append("\n");
+ for (int i = 0; i <= tabI; i++) {
+ sb.append("\t");
+ }
+ child.printSessionTree(tabI + 1, sb);
+ }
+ }
+
+ // Recursively concatenate mShortMethodName with the parent Sessions to create full method
+ // path. Caches this string so that multiple calls for the path will be quick.
+ public String getFullMethodPath() {
+ StringBuilder sb = new StringBuilder();
+ getFullMethodPath(sb);
+ return sb.toString();
+ }
+
+ private synchronized void getFullMethodPath(StringBuilder sb) {
+ // Don't calculate if we have already figured it out!
+ if (!TextUtils.isEmpty(mFullMethodPathCache)) {
+ sb.append(mFullMethodPathCache);
+ return;
+ }
+ Session parentSession = getParentSession();
+ boolean isSessionStarted = false;
+ if (parentSession != null) {
+ // Check to see if the session has been renamed yet. If it has not, then the session
+ // has not been continued.
+ isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName);
+ parentSession.getFullMethodPath(sb);
+ sb.append(SUBSESSION_SEPARATION_CHAR);
+ }
+ sb.append(mShortMethodName);
+
+ if(isSessionStarted) {
+ // Cache this value so that we do not have to do this work next time!
+ // We do not cache the value if the session being evaluated hasn't been continued yet.
+ mFullMethodPathCache = sb.toString();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mSessionId != null ? mSessionId.hashCode() : 0;
+ result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0);
+ result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32));
+ result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32));
+ result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0);
+ result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0);
+ result = 31 * result + (mIsCompleted ? 1 : 0);
+ result = 31 * result + mChildCounter;
+ result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0);
+ result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Session session = (Session) o;
+
+ if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false;
+ if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false;
+ if (mIsCompleted != session.mIsCompleted) return false;
+ if (mChildCounter != session.mChildCounter) return false;
+ if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false;
+ if (mSessionId != null ?
+ !mSessionId.equals(session.mSessionId) : session.mSessionId != null)
+ return false;
+ if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName)
+ : session.mShortMethodName != null)
+ return false;
+ if (mParentSession != null ? !mParentSession.equals(session.mParentSession)
+ : session.mParentSession != null)
+ return false;
+ if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions)
+ : session.mChildSessions != null)
+ return false;
+ return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo)
+ : session.mOwnerInfo == null;
+
+ }
+
+ @Override
+ public String toString() {
+ if (mParentSession != null && mIsStartedFromActiveSession) {
+ // Log.startSession was called from within another active session. Use the parent's
+ // Id instead of the child to reduce confusion.
+ return mParentSession.toString();
+ } else {
+ StringBuilder methodName = new StringBuilder();
+ methodName.append(getFullMethodPath());
+ if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
+ methodName.append("(InCall package: ");
+ methodName.append(mOwnerInfo);
+ methodName.append(")");
+ }
+ return methodName.toString() + "@" + getFullSessionId();
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
new file mode 100644
index 0000000..173e7ee
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -0,0 +1,412 @@
+/*
+ * 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.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.provider.Settings;
+import android.telecom.Log;
+import android.util.Base64;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * TODO: Create better Sessions Documentation
+ * @hide
+ */
+
+public class SessionManager {
+
+ // Currently using 3 letters, So don't exceed 64^3
+ private static final long SESSION_ID_ROLLOVER_THRESHOLD = 262144;
+ // This parameter can be overridden in Telecom's Timeouts class.
+ private static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds
+ private static final String LOGGING_TAG = "Logging";
+ private static final String TIMEOUTS_PREFIX = "telecom.";
+
+ // Synchronized in all method calls
+ private int sCodeEntryCounter = 0;
+ private Context mContext;
+
+ @VisibleForTesting
+ public ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(100);
+ @VisibleForTesting
+ public java.lang.Runnable mCleanStaleSessions = () ->
+ cleanupStaleSessions(getSessionCleanupTimeoutMs());
+ private Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
+
+ // Overridden in LogTest to skip query to ContentProvider
+ private interface ISessionCleanupTimeoutMs {
+ long get();
+ }
+
+ // Overridden in tests to provide test Thread IDs
+ public interface ICurrentThreadId {
+ int get();
+ }
+
+ @VisibleForTesting
+ public ICurrentThreadId mCurrentThreadId = Process::myTid;
+
+ private ISessionCleanupTimeoutMs mSessionCleanupTimeoutMs = () -> {
+ // mContext may be null in some cases, such as testing. For these cases, use the
+ // default value.
+ if (mContext == null) {
+ return DEFAULT_SESSION_TIMEOUT_MS;
+ }
+ return getCleanupTimeout(mContext);
+ };
+
+ // Usage is synchronized on this class.
+ private List<ISessionListener> mSessionListeners = new ArrayList<>();
+
+ public interface ISessionListener {
+ /**
+ * This method is run when a full Session has completed.
+ * @param sessionName The name of the Session that has completed.
+ * @param timeMs The time it took to complete in ms.
+ */
+ void sessionComplete(String sessionName, long timeMs);
+ }
+
+ public interface ISessionIdQueryHandler {
+ String getSessionId();
+ }
+
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ public SessionManager() {
+ }
+
+ private long getSessionCleanupTimeoutMs() {
+ return mSessionCleanupTimeoutMs.get();
+ }
+
+ private synchronized void resetStaleSessionTimer() {
+ mSessionCleanupHandler.removeCallbacksAndMessages(null);
+ // Will be null in Log Testing
+ if (mCleanStaleSessions != null) {
+ mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs());
+ }
+ }
+
+ /**
+ * Determines whether or not to start a new session or continue an existing session based on
+ * the {@link Session.Info} info passed into startSession. If info is null, a new Session is
+ * created. This code must be accompanied by endSession() at the end of the Session.
+ */
+ public synchronized void startSession(Session.Info info, String shortMethodName,
+ String callerIdentification) {
+ // Start a new session normally if the
+ if(info == null) {
+ startSession(shortMethodName, callerIdentification);
+ } else {
+ startExternalSession(info, shortMethodName);
+ }
+ }
+
+ /**
+ * Call at an entry point to the Telecom code to track the session. This code must be
+ * accompanied by a Log.endSession().
+ */
+ public synchronized void startSession(String shortMethodName,
+ String callerIdentification) {
+ resetStaleSessionTimer();
+ int threadId = getCallingThreadId();
+ Session activeSession = mSessionMapper.get(threadId);
+ // We have called startSession within an active session that has not ended... Register this
+ // session as a subsession.
+ if (activeSession != null) {
+ Session childSession = createSubsession(true);
+ continueSession(childSession, shortMethodName);
+ return;
+ } else {
+ // Only Log that we are starting the parent session.
+ Log.d(LOGGING_TAG, Session.START_SESSION);
+ }
+ Session newSession = new Session(getNextSessionID(), shortMethodName,
+ System.currentTimeMillis(), false, callerIdentification);
+ mSessionMapper.put(threadId, newSession);
+ }
+
+ /**
+ * Registers an external Session with the Manager using that external Session's sessionInfo.
+ * Log.endSession will still need to be called at the end of the session.
+ * @param sessionInfo Describes the external Session's information.
+ * @param shortMethodName The method name of the new session that is being started.
+ */
+ public synchronized void startExternalSession(Session.Info sessionInfo,
+ String shortMethodName) {
+ if(sessionInfo == null) {
+ return;
+ }
+
+ int threadId = getCallingThreadId();
+ Session threadSession = mSessionMapper.get(threadId);
+ if (threadSession != null) {
+ // We should never get into a situation where there is already an active session AND
+ // an external session is added. We are just using that active session.
+ Log.w(LOGGING_TAG, "trying to start an external session with a session " +
+ "already active.");
+ return;
+ }
+
+ // Create Session from Info and add to the sessionMapper under this ID.
+ Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId,
+ sessionInfo.shortMethodName, System.currentTimeMillis(),
+ false /*isStartedFromActiveSession*/, null);
+ externalSession.setIsExternal(true);
+ // Mark the external session as already completed, since we have no way of knowing when
+ // the external session actually has completed.
+ externalSession.markSessionCompleted(Session.UNDEFINED);
+ // Track the external session with the SessionMapper so that we can create and continue
+ // an active subsession based on it.
+ mSessionMapper.put(threadId, externalSession);
+ // Create a subsession from this external Session parent node
+ Session childSession = createSubsession();
+ continueSession(childSession, shortMethodName);
+
+ Log.d(LOGGING_TAG, Session.START_SESSION);
+ }
+
+ /**
+ * Notifies the logging system that a subsession will be run at a later point and
+ * allocates the resources. Returns a session object that must be used in
+ * Log.continueSession(...) to start the subsession.
+ */
+ public Session createSubsession() {
+ return createSubsession(false);
+ }
+
+ private synchronized Session createSubsession(boolean isStartedFromActiveSession) {
+ int threadId = getCallingThreadId();
+ Session threadSession = mSessionMapper.get(threadId);
+ if (threadSession == null) {
+ Log.d(LOGGING_TAG, "Log.createSubsession was called with no session " +
+ "active.");
+ return null;
+ }
+ // Start execution time of the session will be overwritten in continueSession(...).
+ Session newSubsession = new Session(threadSession.getNextChildId(),
+ threadSession.getShortMethodName(), System.currentTimeMillis(),
+ isStartedFromActiveSession, null);
+ threadSession.addChild(newSubsession);
+ newSubsession.setParentSession(threadSession);
+
+ if (!isStartedFromActiveSession) {
+ Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " +
+ newSubsession.toString());
+ } else {
+ Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION +
+ " (Invisible subsession)");
+ }
+ return newSubsession;
+ }
+
+ /**
+ * Cancels a subsession that had Log.createSubsession() called on it, but will never have
+ * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned
+ * gracefully instead of being removed by the mSessionCleanupHandler forcefully later.
+ */
+ public synchronized void cancelSubsession(Session subsession) {
+ if (subsession == null) {
+ return;
+ }
+
+ subsession.markSessionCompleted(Session.UNDEFINED);
+ endParentSessions(subsession);
+ }
+
+ /**
+ * Starts the subsession that was created in Log.CreateSubsession. The Log.endSession() method
+ * must be called at the end of this method. The full session will complete when all
+ * subsessions are completed.
+ */
+ public synchronized void continueSession(Session subsession, String shortMethodName) {
+ if (subsession == null) {
+ return;
+ }
+ resetStaleSessionTimer();
+ subsession.setShortMethodName(shortMethodName);
+ subsession.setExecutionStartTimeMs(System.currentTimeMillis());
+ Session parentSession = subsession.getParentSession();
+ if (parentSession == null) {
+ Log.i(LOGGING_TAG, "Log.continueSession was called with no session " +
+ "active for method " + shortMethodName);
+ return;
+ }
+
+ mSessionMapper.put(getCallingThreadId(), subsession);
+ if (!subsession.isStartedFromActiveSession()) {
+ Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION);
+ } else {
+ Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION +
+ " (Invisible Subsession) with Method " + shortMethodName);
+ }
+ }
+
+ /**
+ * Ends the current session/subsession. Must be called after a Log.startSession(...) and
+ * Log.continueSession(...) call.
+ */
+ public synchronized void endSession() {
+ int threadId = getCallingThreadId();
+ Session completedSession = mSessionMapper.get(threadId);
+ if (completedSession == null) {
+ Log.w(LOGGING_TAG, "Log.endSession was called with no session active.");
+ return;
+ }
+
+ completedSession.markSessionCompleted(System.currentTimeMillis());
+ if (!completedSession.isStartedFromActiveSession()) {
+ Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (dur: " +
+ completedSession.getLocalExecutionTime() + " mS)");
+ } else {
+ Log.v(LOGGING_TAG, Session.END_SUBSESSION +
+ " (Invisible Subsession) (dur: " + completedSession.getLocalExecutionTime() +
+ " ms)");
+ }
+ // Remove after completed so that reference still exists for logging the end events
+ Session parentSession = completedSession.getParentSession();
+ mSessionMapper.remove(threadId);
+ endParentSessions(completedSession);
+ // If this subsession was started from a parent session using Log.startSession, return the
+ // ThreadID back to the parent after completion.
+ if (parentSession != null && !parentSession.isSessionCompleted() &&
+ completedSession.isStartedFromActiveSession()) {
+ mSessionMapper.put(threadId, parentSession);
+ }
+ }
+
+ // Recursively deletes all complete parent sessions of the current subsession if it is a leaf.
+ private void endParentSessions(Session subsession) {
+ // Session is not completed or not currently a leaf, so we can not remove because a child is
+ // still running
+ if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) {
+ return;
+ }
+ Session parentSession = subsession.getParentSession();
+ if (parentSession != null) {
+ subsession.setParentSession(null);
+ parentSession.removeChild(subsession);
+ // Report the child session of the external session as being complete to the listeners,
+ // not the external session itself.
+ if (parentSession.isExternal()) {
+ long fullSessionTimeMs =
+ System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
+ notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs);
+ }
+ endParentSessions(parentSession);
+ } else {
+ // All of the subsessions have been completed and it is time to report on the full
+ // running time of the session.
+ long fullSessionTimeMs =
+ System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
+ Log.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs
+ + " ms): " + subsession.toString());
+ if (!subsession.isExternal()) {
+ notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs);
+ }
+ }
+ }
+
+ private void notifySessionCompleteListeners(String methodName, long sessionTimeMs) {
+ for (ISessionListener l : mSessionListeners) {
+ l.sessionComplete(methodName, sessionTimeMs);
+ }
+ }
+
+ public String getSessionId() {
+ Session currentSession = mSessionMapper.get(getCallingThreadId());
+ return currentSession != null ? currentSession.toString() : "";
+ }
+
+ public synchronized void registerSessionListener(ISessionListener l) {
+ if (l != null) {
+ mSessionListeners.add(l);
+ }
+ }
+
+ private synchronized String getNextSessionID() {
+ Integer nextId = sCodeEntryCounter++;
+ if (nextId >= SESSION_ID_ROLLOVER_THRESHOLD) {
+ restartSessionCounter();
+ nextId = sCodeEntryCounter++;
+ }
+ return getBase64Encoding(nextId);
+ }
+
+ private synchronized void restartSessionCounter() {
+ sCodeEntryCounter = 0;
+ }
+
+ private String getBase64Encoding(int number) {
+ byte[] idByteArray = ByteBuffer.allocate(4).putInt(number).array();
+ idByteArray = Arrays.copyOfRange(idByteArray, 2, 4);
+ return Base64.encodeToString(idByteArray, Base64.NO_WRAP | Base64.NO_PADDING);
+ }
+
+ private int getCallingThreadId() {
+ return mCurrentThreadId.get();
+ }
+
+ @VisibleForTesting
+ public synchronized void cleanupStaleSessions(long timeoutMs) {
+ String logMessage = "Stale Sessions Cleaned:\n";
+ boolean isSessionsStale = false;
+ long currentTimeMs = System.currentTimeMillis();
+ // Remove references that are in the Session Mapper (causing GC to occur) on
+ // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS.
+ // If this occurs, then there is most likely a Session active that never had
+ // Log.endSession called on it.
+ for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it =
+ mSessionMapper.entrySet().iterator(); it.hasNext(); ) {
+ ConcurrentHashMap.Entry<Integer, Session> entry = it.next();
+ Session session = entry.getValue();
+ if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) {
+ it.remove();
+ logMessage += session.printFullSessionTree() + "\n";
+ isSessionsStale = true;
+ }
+ }
+ if (isSessionsStale) {
+ Log.w(LOGGING_TAG, logMessage);
+ } else {
+ Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned...");
+ }
+ }
+
+ /**
+ * Returns the amount of time after a Logging session has been started that Telecom is set to
+ * perform a sweep to check and make sure that the session is still not incomplete (stale).
+ */
+ private long getCleanupTimeout(Context context) {
+ return Settings.Secure.getLong(context.getContentResolver(), TIMEOUTS_PREFIX +
+ "stale_session_cleanup_timeout_millis", DEFAULT_SESSION_TIMEOUT_MS);
+ }
+}
diff --git a/telecomm/java/android/telecom/Logging/TimedEvent.java b/telecomm/java/android/telecom/Logging/TimedEvent.java
new file mode 100644
index 0000000..6785e92
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/TimedEvent.java
@@ -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
+ */
+
+package android.telecom.Logging;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public abstract class TimedEvent<T> {
+ public abstract long getTime();
+ public abstract T getKey();
+
+ public static <T> Map<T, Double> averageTimings(Collection<? extends TimedEvent<T>> events) {
+ HashMap<T, Integer> counts = new HashMap<>();
+ HashMap<T, Double> result = new HashMap<>();
+
+ for (TimedEvent<T> entry : events) {
+ if (counts.containsKey(entry.getKey())) {
+ counts.put(entry.getKey(), counts.get(entry.getKey()) + 1);
+ result.put(entry.getKey(), result.get(entry.getKey()) + entry.getTime());
+ } else {
+ counts.put(entry.getKey(), 1);
+ result.put(entry.getKey(), (double) entry.getTime());
+ }
+ }
+
+ for (Map.Entry<T, Double> entry : result.entrySet()) {
+ result.put(entry.getKey(), entry.getValue() / counts.get(entry.getKey()));
+ }
+
+ return result;
+ }
+}
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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 411d881..7506b10 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -57,6 +57,13 @@
// system image, that can be added in packages/apps/CarrierConfig.
/**
+ * This flag specifies whether VoLTE availability is based on provisioning. By default this is
+ * false.
+ */
+ public static final String
+ KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
+
+ /**
* Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED
* events from the Sim.
* If true, this will prevent the IccNetworkDepersonalizationPanel from being shown, and
@@ -116,6 +123,13 @@
public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
/**
+ * Determines if the carrier requires converting the destination number before sending out an
+ * SMS. Certain networks and numbering plans require different formats.
+ */
+ public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL=
+ "sms_requires_destination_number_conversion_bool";
+
+ /**
* If true, show an onscreen "Dial" button in the dialer. In practice this is used on all
* platforms, even the ones with hard SEND/END keys, but for maximum flexibility it's controlled
* by a flag here (which can be overridden on a per-product basis.)
@@ -182,6 +196,13 @@
public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
/**
+ * Since the default voicemail number is empty, if a SIM card does not have a voicemail number
+ * available the user cannot use voicemail. This flag allows the user to edit the voicemail
+ * number in such cases, and is false by default.
+ */
+ public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL= "editable_voicemail_number_bool";
+
+ /**
* Determine whether the voicemail notification is persistent in the notification bar. If true,
* the voicemail notifications cannot be dismissed from the notification bar.
*/
@@ -200,6 +221,14 @@
KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
/**
+ * List of RIL radio technologies (See {@link ServiceState} {@code RIL_RADIO_TECHNOLOGY_*}
+ * constants) which support only a single data connection at a time. Some carriers do not
+ * support multiple pdp on UMTS.
+ */
+ public static final String
+ KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
+
+ /**
* Override the platform's notion of a network operator being considered roaming.
* Value is string array of MCCMNCs to be considered roaming for 3GPP RATs.
*/
@@ -312,13 +341,23 @@
"carrier_wfc_supports_wifi_only_bool";
/**
- * Default WFC_IMS_mode 0: WIFI_ONLY
- * 1: CELLULAR_PREFERRED
- * 2: WIFI_PREFERRED
+ * Default WFC_IMS_MODE for home network 0: WIFI_ONLY
+ * 1: CELLULAR_PREFERRED
+ * 2: WIFI_PREFERRED
* @hide
*/
public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT =
"carrier_default_wfc_ims_mode_int";
+
+ /**
+ * Default WFC_IMS_MODE for roaming
+ * See {@link KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} for valid values.
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT =
+ "carrier_default_wfc_ims_roaming_mode_int";
+
/**
* Default WFC_IMS_enabled: true VoWiFi by default is on
* false VoWiFi by default is off
@@ -729,6 +768,16 @@
public static final String KEY_CARRIER_ADDITIONAL_CBS_CHANNELS_STRINGS =
"carrier_additional_cbs_channels_strings";
+ /**
+ * Indicates whether STK LAUNCH_BROWSER command is disabled.
+ * If {@code true}, then the browser will not be launched
+ * on UI for the LAUNCH_BROWSER STK command.
+ * @hide
+ */
+ public static final String KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL =
+ "stk_disable_launch_browser_bool";
+
+
// These variables are used by the MMS service and exposed through another API, {@link
// SmsManager}. The variable names and string values are copied from there.
public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
@@ -1011,6 +1060,30 @@
*/
public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
+ /**
+ * Determine whether user can change Wi-Fi Calling preference in roaming.
+ * {@code false} - roaming preference {@link KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT} is
+ * the same as home preference {@link KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT}
+ * and cannot be changed.
+ * {@code true} - roaming preference can be changed by user independently.
+ *
+ * @hide
+ */
+ public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL =
+ "editable_wfc_roaming_mode_bool";
+
+ /**
+ * Determine whether current lpp_mode used for E-911 needs to be kept persistently.
+ * {@code false} - not keeping the lpp_mode means using default configuration of gps.conf
+ * when sim is not presented.
+ * {@code true} - current lpp_profile of carrier will be kepted persistently
+ * even after sim is removed.
+ *
+ * @hide
+ */
+ public static final String KEY_PERSIST_LPP_MODE_BOOL = "persist_lpp_mode_bool";
+
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1034,6 +1107,7 @@
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL, false);
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);
+ sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
@@ -1051,16 +1125,20 @@
sDefaults.putBoolean(KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false);
+
+ sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false);
sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
sDefaults.putBoolean(KEY_PREFER_2G_BOOL, true);
sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_CDMA_CHOICES_BOOL, false);
+ sDefaults.putBoolean(KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL, true);
sDefaults.putBoolean(KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+ sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false);
@@ -1102,6 +1180,15 @@
sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
+ sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
+ new int[]{
+ 4, /* IS95A */
+ 5, /* IS95B */
+ 6, /* 1xRTT */
+ 7, /* EVDO_0 */
+ 8, /* EVDO_A */
+ 12 /* EVDO_B */
+ });
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
@@ -1189,6 +1276,9 @@
sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false);
sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false);
sDefaults.putStringArray(FILTERED_CNAP_NAMES_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false);
+ sDefaults.putBoolean(KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL, false);
+ sDefaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, false);
}
/**
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/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 05cb31e..d4104bd 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -176,13 +176,13 @@
// However, if there is any code that this Handler calls (such as in
// super.handleMessage) that DOES place unexpected messages on the
// queue, then we need pass these messages on.
- if (DBG) Rlog.d(LOG_TAG, "Unexpected command (CookieWrapper is null): " + msg.what +
+ Rlog.i(LOG_TAG, "Unexpected command (CookieWrapper is null): " + msg.what +
" ignored by CallerInfoWorkerHandler, passing onto parent.");
super.handleMessage(msg);
} else {
- if (DBG) Rlog.d(LOG_TAG, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
+ Rlog.d(LOG_TAG, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
" command: " + msg.what + " query URI: " + sanitizeUriToString(args.uri));
switch (cw.event) {
@@ -239,7 +239,7 @@
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- if (DBG) Rlog.d(LOG_TAG, "##### onQueryComplete() ##### query complete for token: " + token);
+ Rlog.d(LOG_TAG, "##### onQueryComplete() ##### query complete for token: " + token);
//get the cookie and notify the listener.
CookieWrapper cw = (CookieWrapper) cookie;
@@ -248,7 +248,7 @@
// from within this code.
// However, if there is any code that calls this method, we should
// check the parameters to make sure they're viable.
- if (DBG) Rlog.d(LOG_TAG, "Cookie is null, ignoring onQueryComplete() request.");
+ Rlog.i(LOG_TAG, "Cookie is null, ignoring onQueryComplete() request.");
if (cursor != null) {
cursor.close();
}
@@ -333,9 +333,11 @@
//notify the listener that the query is complete.
if (cw.listener != null) {
- if (DBG) Rlog.d(LOG_TAG, "notifying listener: " + cw.listener.getClass().toString() +
+ Rlog.d(LOG_TAG, "notifying listener: " + cw.listener.getClass().toString() +
" for token: " + token + mCallerInfo);
cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
+ } else {
+ Rlog.w(LOG_TAG, "There is no listener to notify for this query.");
}
if (cursor != null) {
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index a91e9be..891b8a1a 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -462,4 +462,5 @@
int RIL_UNSOL_STK_CC_ALPHA_NOTIFY = 1044;
int RIL_UNSOL_LCEDATA_RECV = 1045;
int RIL_UNSOL_PCO_DATA = 1046;
+ int RIL_UNSOL_MODEM_RESTART = 1047;
}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index a4aab7c..da27ea9 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -87,6 +87,11 @@
}
private Test[] mTests = new Test[] {
+ new Test("cancel all") {
+ public void run() {
+ mNM.cancelAll();
+ }
+ },
new Test("Phone call") {
public void run()
{
diff --git a/tests/UiBench/Android.mk b/tests/UiBench/Android.mk
index f65d109..be9a541 100644
--- a/tests/UiBench/Android.mk
+++ b/tests/UiBench/Android.mk
@@ -2,6 +2,8 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := 24
+LOCAL_MIN_SDK_VERSION := 21
# omit gradle 'build' dir
LOCAL_SRC_FILES := $(call all-java-files-under,src)
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index cb5f6c7..29154aa 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -102,10 +102,18 @@
<activity
android:name=".SlowBindRecyclerViewActivity"
android:label="General/Slow Bind RecyclerView" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="com.android.test.uibench.TEST" />
- </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.uibench.TEST" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".SlowNestedRecyclerViewActivity"
+ android:label="General/Slow Nested RecyclerView" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.uibench.TEST" />
+ </intent-filter>
</activity>
<activity
android:name=".ActivityTransition"
@@ -141,6 +149,14 @@
<category android:name="com.android.test.uibench.TEST" />
</intent-filter>
</activity>
+ <activity
+ android:name=".RenderingJitter"
+ android:label="Rendering/Jitter" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.uibench.TEST" />
+ </intent-filter>
+ </activity>
<!-- Inflation -->
<activity
diff --git a/tests/UiBench/res/layout/rendering_jitter.xml b/tests/UiBench/res/layout/rendering_jitter.xml
new file mode 100644
index 0000000..aaa7551
--- /dev/null
+++ b/tests/UiBench/res/layout/rendering_jitter.xml
@@ -0,0 +1,56 @@
+<?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
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/jitter_mma"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true" />
+
+ <TextView android:id="@+id/totalish_mma"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true" />
+
+ <TextView android:id="@+id/ui_frametime_mma"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true" />
+
+ <TextView android:id="@+id/rt_frametime_mma"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true" />
+
+ <TextView android:id="@+id/total_mma"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true" />
+
+ <View android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1" />
+
+ <view class="com.android.test.uibench.RenderingJitter$PointGraphView"
+ android:id="@+id/graph"
+ android:layout_height="200dp"
+ android:layout_width="match_parent" />
+
+</LinearLayout>
diff --git a/tests/UiBench/src/com/android/test/uibench/RenderingJitter.java b/tests/UiBench/src/com/android/test/uibench/RenderingJitter.java
new file mode 100644
index 0000000..e2a9bcb
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/RenderingJitter.java
@@ -0,0 +1,366 @@
+/*
+ * 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.test.uibench;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.FrameMetrics;
+import android.view.View;
+import android.view.Window;
+import android.view.Window.OnFrameMetricsAvailableListener;
+import android.view.animation.AnimationUtils;
+import android.widget.TextView;
+
+public class RenderingJitter extends Activity {
+ private TextView mJitterReport;
+ private TextView mUiFrameTimeReport;
+ private TextView mRenderThreadTimeReport;
+ private TextView mTotalFrameTimeReport;
+ private TextView mMostlyTotalFrameTimeReport;
+ private PointGraphView mGraph;
+
+ private static Handler sMetricsHandler;
+ static {
+ HandlerThread thread = new HandlerThread("frameMetricsListener");
+ thread.start();
+ sMetricsHandler = new Handler(thread.getLooper());
+ }
+
+ private Handler mUpdateHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case R.id.jitter_mma:
+ mJitterReport.setText((CharSequence) msg.obj);
+ break;
+ case R.id.totalish_mma:
+ mMostlyTotalFrameTimeReport.setText((CharSequence) msg.obj);
+ break;
+ case R.id.ui_frametime_mma:
+ mUiFrameTimeReport.setText((CharSequence) msg.obj);
+ break;
+ case R.id.rt_frametime_mma:
+ mRenderThreadTimeReport.setText((CharSequence) msg.obj);
+ break;
+ case R.id.total_mma:
+ mTotalFrameTimeReport.setText((CharSequence) msg.obj);
+ break;
+ case R.id.graph:
+ mGraph.addJitterSample(msg.arg1, msg.arg2);
+ break;
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.rendering_jitter);
+ View content = findViewById(android.R.id.content);
+ content.setBackground(new AnimatedBackgroundDrawable());
+ content.setKeepScreenOn(true);
+ mJitterReport = (TextView) findViewById(R.id.jitter_mma);
+ mMostlyTotalFrameTimeReport = (TextView) findViewById(R.id.totalish_mma);
+ mUiFrameTimeReport = (TextView) findViewById(R.id.ui_frametime_mma);
+ mRenderThreadTimeReport = (TextView) findViewById(R.id.rt_frametime_mma);
+ mTotalFrameTimeReport = (TextView) findViewById(R.id.total_mma);
+ mGraph = (PointGraphView) findViewById(R.id.graph);
+ mJitterReport.setText("abcdefghijklmnopqrstuvwxyz");
+ mMostlyTotalFrameTimeReport.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ mUiFrameTimeReport.setText("0123456789");
+ mRenderThreadTimeReport.setText(",.!()[]{};");
+ getWindow().addOnFrameMetricsAvailableListener(mMetricsListener, sMetricsHandler);
+ }
+
+ public static final class PointGraphView extends View {
+ private static final float[] JITTER_LINES_MS = {
+ .5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 5.0f
+ };
+ private static final String[] JITTER_LINES_LABELS = makeLabels(JITTER_LINES_MS);
+ private static final int[] JITTER_LINES_COLORS = new int[] {
+ 0xFF00E676, 0xFFFFF176, 0xFFFDD835, 0xFFFBC02D, 0xFFF9A825,
+ 0xFFF57F17, 0xFFDD2C00
+ };
+ private Paint mPaint = new Paint();
+ private float[] mJitterYs = new float[JITTER_LINES_MS.length];
+ private float mLabelWidth;
+ private float mLabelHeight;
+ private float mDensity;
+ private float mGraphScale;
+ private float mGraphMaxMs;
+
+ private float[] mJitterPoints;
+ private float[] mJitterAvgPoints;
+
+ public PointGraphView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWillNotDraw(false);
+ mDensity = context.getResources().getDisplayMetrics().density;
+ mPaint.setTextSize(dp(10));
+ Rect textBounds = new Rect();
+ mPaint.getTextBounds("8.8", 0, 3, textBounds);
+ mLabelWidth = textBounds.width() + dp(2);
+ mLabelHeight = textBounds.height();
+ }
+
+ public void addJitterSample(int jitterUs, int jitterUsAvg) {
+ for (int i = 1; i < mJitterPoints.length - 2; i += 2) {
+ mJitterPoints[i] = mJitterPoints[i + 2];
+ mJitterAvgPoints[i] = mJitterAvgPoints[i + 2];
+ }
+ mJitterPoints[mJitterPoints.length - 1] =
+ getHeight() - mGraphScale * (jitterUs / 1000.0f);
+ mJitterAvgPoints[mJitterAvgPoints.length - 1] =
+ getHeight() - mGraphScale * (jitterUsAvg / 1000.0f);
+ invalidate();
+ }
+
+ private float dp(float dp) {
+ return mDensity * dp;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawColor(0x90000000);
+ int h = getHeight();
+ int w = getWidth();
+ mPaint.setColor(Color.WHITE);
+ mPaint.setStrokeWidth(dp(1));
+ canvas.drawLine(mLabelWidth, 0, mLabelWidth, h, mPaint);
+ for (int i = 0; i < JITTER_LINES_LABELS.length; i++) {
+ canvas.drawText(JITTER_LINES_LABELS[i],
+ 0, (float) Math.floor(mJitterYs[i] + mLabelHeight * .5f), mPaint);
+ }
+ for (int i = 0; i < JITTER_LINES_LABELS.length; i++) {
+ mPaint.setColor(JITTER_LINES_COLORS[i]);
+ canvas.drawLine(mLabelWidth, mJitterYs[i], w, mJitterYs[i], mPaint);
+ }
+ mPaint.setStrokeWidth(dp(2));
+ mPaint.setColor(Color.WHITE);
+ canvas.drawPoints(mJitterPoints, mPaint);
+ mPaint.setColor(0xFF2196F3);
+ canvas.drawPoints(mJitterAvgPoints, mPaint);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ int graphWidth = (int) ((w - mLabelWidth - dp(1)) / mDensity);
+ float[] oldJitterPoints = mJitterPoints;
+ float[] oldJitterAvgPoints = mJitterAvgPoints;
+ mJitterPoints = new float[graphWidth * 2];
+ mJitterAvgPoints = new float[graphWidth * 2];
+ for (int i = 0; i < mJitterPoints.length; i += 2) {
+ mJitterPoints[i] = mLabelWidth + (i / 2 + 1) * mDensity;
+ mJitterAvgPoints[i] = mJitterPoints[i];
+ }
+ if (oldJitterPoints != null) {
+ int newIndexShift = Math.max(mJitterPoints.length - oldJitterPoints.length, 0);
+ int oldIndexShift = oldJitterPoints.length - mJitterPoints.length;
+ for (int i = 1 + newIndexShift; i < mJitterPoints.length; i += 2) {
+ mJitterPoints[i] = oldJitterPoints[i + oldIndexShift];
+ mJitterAvgPoints[i] = oldJitterAvgPoints[i + oldIndexShift];
+ }
+ }
+ mGraphMaxMs = JITTER_LINES_MS[JITTER_LINES_MS.length - 1] + .5f;
+ mGraphScale = (h / mGraphMaxMs);
+ for (int i = 0; i < JITTER_LINES_MS.length; i++) {
+ mJitterYs[i] = (float) Math.floor(h - mGraphScale * JITTER_LINES_MS[i]);
+ }
+ }
+
+ private static String[] makeLabels(float[] divisions) {
+ String[] ret = new String[divisions.length];
+ for (int i = 0; i < divisions.length; i++) {
+ ret[i] = Float.toString(divisions[i]);
+ }
+ return ret;
+ }
+ }
+
+ private final OnFrameMetricsAvailableListener mMetricsListener = new OnFrameMetricsAvailableListener() {
+ private final static double WEIGHT = 40;
+ private long mPreviousFrameTotal;
+ private double mJitterMma;
+ private double mUiFrametimeMma;
+ private double mRtFrametimeMma;
+ private double mTotalFrametimeMma;
+ private double mMostlyTotalFrametimeMma;
+ private boolean mNeedsFirstValues = true;
+
+ @Override
+ public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
+ int dropCountSinceLastInvocation) {
+ if (frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1) {
+ return;
+ }
+
+ long uiDuration = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION)
+ + frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION)
+ + frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION)
+ + frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
+ long rtDuration = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION)
+ + frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
+ long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
+ long jitter = Math.abs(totalDuration - mPreviousFrameTotal);
+ if (mNeedsFirstValues) {
+ mJitterMma = 0;
+ mUiFrametimeMma = uiDuration;
+ mRtFrametimeMma = rtDuration;
+ mTotalFrametimeMma = totalDuration;
+ mMostlyTotalFrametimeMma = uiDuration + rtDuration;
+ mNeedsFirstValues = false;
+ } else {
+ mJitterMma = add(mJitterMma, jitter);
+ mUiFrametimeMma = add(mUiFrametimeMma, uiDuration);
+ mRtFrametimeMma = add(mRtFrametimeMma, rtDuration);
+ mTotalFrametimeMma = add(mTotalFrametimeMma, totalDuration);
+ mMostlyTotalFrametimeMma = add(mMostlyTotalFrametimeMma, uiDuration + rtDuration);
+ }
+ mPreviousFrameTotal = totalDuration;
+ mUpdateHandler.obtainMessage(R.id.jitter_mma,
+ String.format("Jitter: %.3fms", toMs(mJitterMma))).sendToTarget();
+ mUpdateHandler.obtainMessage(R.id.totalish_mma,
+ String.format("CPU-total duration: %.3fms", toMs(mMostlyTotalFrametimeMma))).sendToTarget();
+ mUpdateHandler.obtainMessage(R.id.ui_frametime_mma,
+ String.format("UI duration: %.3fms", toMs(mUiFrametimeMma))).sendToTarget();
+ mUpdateHandler.obtainMessage(R.id.rt_frametime_mma,
+ String.format("RT duration: %.3fms", toMs(mRtFrametimeMma))).sendToTarget();
+ mUpdateHandler.obtainMessage(R.id.total_mma,
+ String.format("Total duration: %.3fms", toMs(mTotalFrametimeMma))).sendToTarget();
+ mUpdateHandler.obtainMessage(R.id.graph, (int) (jitter / 1000),
+ (int) (mJitterMma / 1000)).sendToTarget();
+ }
+
+ double add(double previous, double today) {
+ return (((WEIGHT - 1) * previous) + today) / WEIGHT;
+ }
+
+ double toMs(double val) {
+ return val / 1000000;
+ }
+ };
+
+ private static final class AnimatedBackgroundDrawable extends Drawable {
+ private static final int FROM_COLOR = 0xFF18FFFF;
+ private static final int TO_COLOR = 0xFF40C4FF;
+ private static final int DURATION = 1400;
+
+ private final Paint mPaint;
+ private boolean mReverse;
+ private long mStartTime;
+ private int mColor;
+
+ private boolean mReverseX;
+ private boolean mReverseY;
+ private float mX;
+ private float mY;
+ private float mRadius;
+ private float mMoveStep = 10.0f;
+
+ public AnimatedBackgroundDrawable() {
+ mPaint = new Paint();
+ mPaint.setColor(0xFFFFFF00);
+ mPaint.setAntiAlias(true);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ stepColor();
+ canvas.drawColor(mColor);
+
+ mX += (mReverseX ? -mMoveStep : mMoveStep);
+ mY += (mReverseY ? -mMoveStep : mMoveStep);
+ clampXY();
+ canvas.drawCircle(mX, mY, mRadius, mPaint);
+
+ invalidateSelf();
+ }
+
+ private void clampXY() {
+ if (mX <= mRadius) {
+ mReverseX = false;
+ mX = mRadius;
+ }
+ if (mY <= mRadius) {
+ mReverseY = false;
+ mY = mRadius;
+ }
+ float maxX = getBounds().width() - mRadius;
+ if (mX >= maxX) {
+ mReverseX = true;
+ mX = maxX;
+ }
+ float maxY = getBounds().height() - mRadius;
+ if (mY >= maxY) {
+ mReverseY = true;
+ mY = maxY;
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mMoveStep = Math.min(bounds.width(), bounds.height()) / 130.0f;
+ mRadius = Math.min(bounds.width(), bounds.height()) / 20.0f;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+
+ private void stepColor() {
+ if (mStartTime == 0) {
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ }
+ float frac = (AnimationUtils.currentAnimationTimeMillis() - mStartTime)
+ / (float) DURATION;
+ if (frac > 1.0f) frac = 1.0f;
+ int dest = mReverse ? FROM_COLOR : TO_COLOR;
+ int src = mReverse ? TO_COLOR : FROM_COLOR;
+ int r = (int) (Color.red(src) + (Color.red(dest) - Color.red(src)) * frac);
+ int g = (int) (Color.green(src) + (Color.green(dest) - Color.green(src)) * frac);
+ int b = (int) (Color.blue(src) + (Color.blue(dest) - Color.blue(src)) * frac);
+ mColor = Color.rgb(r, g, b);
+ if (frac == 1.0f) {
+ mStartTime = 0;
+ mReverse = !mReverse;
+ }
+ }
+ }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/SlowNestedRecyclerViewActivity.java b/tests/UiBench/src/com/android/test/uibench/SlowNestedRecyclerViewActivity.java
new file mode 100644
index 0000000..305c051
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/SlowNestedRecyclerViewActivity.java
@@ -0,0 +1,181 @@
+/*
+ * 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.test.uibench;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.test.uibench.recyclerview.RvCompatListActivity;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+public class SlowNestedRecyclerViewActivity extends RvCompatListActivity {
+ private static final int OUTER_ITEM_COUNT = 100;
+ private static final int INNER_ITEM_COUNT = 20;
+
+ private static final long INNER_ITEM_CREATE_NS = TimeUnit.MILLISECONDS.toNanos(6);
+ private static final long INNER_ITEM_BIND_NS = TimeUnit.MILLISECONDS.toNanos(1);
+ private static final long INNER_ITEM_ATTACH_NS = TimeUnit.MILLISECONDS.toNanos(1);
+
+ private static final long OUTER_ITEM_CREATE_NS = TimeUnit.MILLISECONDS.toNanos(3);
+ private static final long OUTER_ITEM_BIND_NS = TimeUnit.MILLISECONDS.toNanos(1);
+ private static final long OUTER_ITEM_ATTACH_NS = TimeUnit.MILLISECONDS.toNanos(1);
+
+ private SizeData mSizeData;
+
+ private static class SizeData {
+ final int innerItemWidth;
+ final int innerItemHeight;
+ final int headerHeight;
+
+ SizeData(Resources resources) {
+ innerItemWidth = (int) (resources.getDisplayMetrics().widthPixels / 3.3f);
+ innerItemHeight = (int) (innerItemWidth * 1.6f);
+ headerHeight = (int) (resources.getDisplayMetrics().heightPixels * 0.5f);
+ }
+ }
+
+ private SizeData getSizeData(Resources resources) {
+ if (mSizeData == null) {
+ mSizeData = new SizeData(resources);
+ }
+ return mSizeData;
+ }
+
+ @Override
+ protected RecyclerView.LayoutManager createLayoutManager(Context context) {
+ return new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
+ }
+
+ @Override
+ protected RecyclerView.Adapter createAdapter() {
+ return new OuterAdapter();
+ }
+
+ private class InnerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final long start = System.nanoTime();
+
+ final float density = parent.getResources().getDisplayMetrics().density;
+ View view = new View(parent.getContext()) {
+ @Override
+ protected void onAttachedToWindow() {
+ final long start = System.nanoTime();
+ super.onAttachedToWindow();
+ while (System.nanoTime() - start < INNER_ITEM_ATTACH_NS);
+ }
+ };
+
+ SizeData sizeData = getSizeData(parent.getResources());
+ view.setMinimumWidth(sizeData.innerItemWidth);
+ view.setMinimumHeight(sizeData.innerItemHeight);
+
+ GradientDrawable bg = new GradientDrawable();
+ bg.setCornerRadius(10 * density);
+ bg.setColor(Color.BLACK);
+ final int pad = (int)(10 * density);
+ view.setPadding(pad, pad, pad, pad);
+ view.setBackgroundDrawable(new InsetDrawable(bg, pad));
+ RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(view) {};
+
+ while (System.nanoTime() - start < INNER_ITEM_CREATE_NS);
+ return holder;
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ final long start = System.nanoTime();
+ while (System.nanoTime() - start < INNER_ITEM_BIND_NS);
+ }
+
+ @Override
+ public int getItemCount() { return INNER_ITEM_COUNT; }
+ }
+
+ private class OuterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ static final int TYPE_HEADER = 0;
+ static final int TYPE_RECYCLER = 1;
+
+ ArrayList<InnerAdapter> mAdapters = new ArrayList<>();
+ RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
+
+ OuterAdapter() {
+ for (int i = 0; i < OUTER_ITEM_COUNT; i++) {
+ mAdapters.add(new InnerAdapter());
+ }
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ SizeData sizeData = getSizeData(parent.getResources());
+ if (viewType == TYPE_HEADER) {
+ View view = new View(parent.getContext());
+ view.setMinimumHeight(sizeData.headerHeight);
+ return new RecyclerView.ViewHolder(view) {};
+ } else {
+ final long start = System.nanoTime();
+
+ RecyclerView rv = new RecyclerView(parent.getContext()) {
+ @Override
+ protected void onAttachedToWindow() {
+ final long start = System.nanoTime();
+ super.onAttachedToWindow();
+ while (System.nanoTime() - start < OUTER_ITEM_ATTACH_NS);
+
+ }
+ };
+
+ rv.setLayoutParams(new RecyclerView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, sizeData.innerItemHeight));
+ rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
+ LinearLayoutManager.HORIZONTAL, false));
+ rv.setRecycledViewPool(mSharedPool);
+ RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(rv) {};
+
+ while (System.nanoTime() - start < OUTER_ITEM_CREATE_NS);
+ return holder;
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (getItemViewType(position) == TYPE_RECYCLER) {
+ final long start = System.nanoTime();
+ ((RecyclerView)holder.itemView).setAdapter(mAdapters.get(position));
+ while (System.nanoTime() - start < OUTER_ITEM_BIND_NS);
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position == 0 ? TYPE_HEADER : TYPE_RECYCLER;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAdapters.size();
+ }
+ }
+}
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
index cbc6c76..5fdf0dd 100644
--- a/tests/VoiceInteraction/AndroidManifest.xml
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.voiceinteraction">
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="25" />
+
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.READ_LOGS" />
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/aapt/Command.cpp b/tools/aapt/Command.cpp
index fb1370e..9b62e14 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1281,7 +1281,7 @@
const size_t NL = locales.size();
for (size_t i=0; i<NL; i++) {
const char* localeStr = locales[i].string();
- assets.setLocale(localeStr != NULL ? localeStr : "");
+ assets.setConfiguration(config, localeStr != NULL ? localeStr : "");
String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR,
&error);
if (llabel != "") {
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index aea16c7..5f586a1 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -833,6 +833,7 @@
bpp = 4;
} else {
printf("Unknown color type %d.\n", color_type);
+ return;
}
for (j = 0; j < h; j++) {
diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp
index 54a8e9c..5339285 100644
--- a/tools/aapt/ZipEntry.cpp
+++ b/tools/aapt/ZipEntry.cpp
@@ -23,9 +23,10 @@
#include "ZipEntry.h"
#include <utils/Log.h>
+#include <assert.h>
#include <stdio.h>
#include <string.h>
-#include <assert.h>
+#include <time.h>
using namespace android;
diff --git a/tools/aapt2/.clang-format b/tools/aapt2/.clang-format
new file mode 100644
index 0000000..545366a
--- /dev/null
+++ b/tools/aapt2/.clang-format
@@ -0,0 +1,2 @@
+BasedOnStyle: Google
+
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 60114fb..197884dc 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -24,7 +24,10 @@
sources := \
compile/IdAssigner.cpp \
compile/InlineXmlFormatParser.cpp \
+ compile/NinePatch.cpp \
compile/Png.cpp \
+ compile/PngChunkFilter.cpp \
+ compile/PngCrunch.cpp \
compile/PseudolocaleGenerator.cpp \
compile/Pseudolocalizer.cpp \
compile/XmlIdCollector.cpp \
@@ -34,12 +37,14 @@
flatten/XmlFlattener.cpp \
io/File.cpp \
io/FileSystem.cpp \
+ io/Io.cpp \
io/ZipArchive.cpp \
link/AutoVersioner.cpp \
link/ManifestFixer.cpp \
link/ProductFilter.cpp \
link/PrivateAttributeMover.cpp \
link/ReferenceLinker.cpp \
+ link/ResourceDeduper.cpp \
link/TableMerger.cpp \
link/VersionCollapser.cpp \
link/XmlNamespaceRemover.cpp \
@@ -56,6 +61,7 @@
util/Util.cpp \
ConfigDescription.cpp \
Debug.cpp \
+ DominatorTree.cpp \
Flags.cpp \
java/AnnotationProcessor.cpp \
java/ClassDefinition.cpp \
@@ -77,11 +83,12 @@
sources += Format.proto
-sourcesJni :=
+sourcesJni := jni/aapt2_jni.cpp
testSources := \
compile/IdAssigner_test.cpp \
compile/InlineXmlFormatParser_test.cpp \
+ compile/NinePatch_test.cpp \
compile/PseudolocaleGenerator_test.cpp \
compile/Pseudolocalizer_test.cpp \
compile/XmlIdCollector_test.cpp \
@@ -93,6 +100,7 @@
link/PrivateAttributeMover_test.cpp \
link/ProductFilter_test.cpp \
link/ReferenceLinker_test.cpp \
+ link/ResourceDeduper_test.cpp \
link/TableMerger_test.cpp \
link/VersionCollapser_test.cpp \
link/XmlNamespaceRemover_test.cpp \
@@ -106,6 +114,7 @@
util/StringPiece_test.cpp \
util/Util_test.cpp \
ConfigDescription_test.cpp \
+ DominatorTree_test.cpp \
java/AnnotationProcessor_test.cpp \
java/JavaClassGenerator_test.cpp \
java/ManifestClassGenerator_test.cpp \
@@ -149,10 +158,10 @@
hostLdLibs_linux := -lz
hostLdLibs_darwin := -lz
-cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
+cFlags := -Wall -Werror -Wno-unused-parameter
cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS
cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error.
-cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
+cppFlags := -Wno-missing-field-initializers -fno-exceptions -fno-rtti
protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST)
# ==========================================================
@@ -185,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..1e488f7 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -17,10 +17,10 @@
#ifndef AAPT_APP_INFO_H
#define AAPT_APP_INFO_H
-#include "util/Maybe.h"
-
#include <string>
+#include "util/Maybe.h"
+
namespace aapt {
/**
@@ -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> min_sdk_version;
- /**
- * The Version code of the app.
- */
- Maybe<uint32_t> versionCode;
+ /**
+ * The Version code of the app.
+ */
+ Maybe<uint32_t> version_code;
- /**
- * The revision code of the app.
- */
- Maybe<uint32_t> revisionCode;
+ /**
+ * The revision code of the app.
+ */
+ Maybe<uint32_t> revision_code;
};
-} // 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 c1697e7..289919a3 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -15,778 +15,887 @@
*/
#include "ConfigDescription.h"
+
+#include <string>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+
#include "Locale.h"
#include "SdkConstants.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <string>
-#include <vector>
-
namespace aapt {
using android::ResTable_config;
static const char* kWildcardName = "any";
-const ConfigDescription& ConfigDescription::defaultConfig() {
- static ConfigDescription config = {};
- return config;
+const ConfigDescription& ConfigDescription::DefaultConfig() {
+ 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, '-');
+bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+ std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
- ConfigDescription config;
- ssize_t partsConsumed = 0;
- LocaleValue locale;
+ ConfigDescription config;
+ ssize_t parts_consumed = 0;
+ LocaleValue locale;
- const auto partsEnd = parts.end();
- auto partIter = parts.begin();
+ const auto parts_end = parts.end();
+ auto part_iter = parts.begin();
- if (str.size() == 0) {
- goto success;
+ if (str.size() == 0) {
+ goto success;
+ }
+
+ if (parseMcc(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
}
+ }
- if (parseMcc(partIter->c_str(), &config)) {
- ++partIter;
- if (partIter == partsEnd) {
- goto success;
- }
+ if (parseMnc(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ 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.
+ parts_consumed = locale.InitFromParts(part_iter, parts_end);
+ if (parts_consumed < 0) {
return false;
+ } else {
+ locale.WriteTo(&config);
+ part_iter += parts_consumed;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseLayoutDirection(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseSmallestScreenWidthDp(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseScreenWidthDp(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseScreenHeightDp(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseScreenLayoutSize(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseScreenLayoutLong(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseScreenRound(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseOrientation(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseUiModeType(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseUiModeNight(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseDensity(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseTouchscreen(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseKeysHidden(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseKeyboard(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseNavHidden(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseNavigation(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseScreenSize(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseVersion(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ // Unrecognized.
+ return false;
success:
- if (out != NULL) {
- applyVersionForCompatibility(&config);
- *out = config;
- }
+ if (out != NULL) {
+ ApplyVersionForCompatibility(&config);
+ *out = config;
+ }
+ return true;
+}
+
+void ConfigDescription::ApplyVersionForCompatibility(
+ ConfigDescription* config) {
+ uint16_t min_sdk = 0;
+ if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+ min_sdk = SDK_MARSHMALLOW;
+ } else if (config->density == ResTable_config::DENSITY_ANY) {
+ min_sdk = SDK_LOLLIPOP;
+ } else if (config->smallestScreenWidthDp !=
+ ResTable_config::SCREENWIDTH_ANY ||
+ config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY ||
+ config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+ min_sdk = 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) {
+ min_sdk = 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) {
+ min_sdk = SDK_DONUT;
+ }
+
+ if (min_sdk > config->sdkVersion) {
+ config->sdkVersion = min_sdk;
+ }
+}
+
+ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const {
+ 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);
}
-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;
- }
+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;
}
-ConfigDescription ConfigDescription::copyWithoutSdkVersion() const {
- ConfigDescription copy = *this;
- copy.sdkVersion = 0;
- return copy;
+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);
}
-} // namespace aapt
+bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const {
+ return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 6858c62..97d0f38 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -17,11 +17,12 @@
#ifndef AAPT_CONFIG_DESCRIPTION_H
#define AAPT_CONFIG_DESCRIPTION_H
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
#include <ostream>
+#include "androidfw/ResourceTypes.h"
+
+#include "util/StringPiece.h"
+
namespace aapt {
/*
@@ -29,108 +30,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);
- 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;
+ 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;
+
+ /**
+ * 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 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 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::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..c331dc0 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,84 +15,88 @@
*/
#include "ConfigDescription.h"
+
+#include <string>
+
#include "SdkConstants.h"
#include "test/Test.h"
#include "util/StringPiece.h"
-#include <string>
-
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..60b01e3 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -15,10 +15,6 @@
*/
#include "Debug.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "util/Util.h"
-#include "ValueVisitor.h"
#include <algorithm>
#include <iostream>
@@ -28,278 +24,290 @@
#include <set>
#include <vector>
+#include "android-base/logging.h"
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "util/Util.h"
+
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->type_mask & 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& parent_ref = style->parent.value();
+ std::cout << " parent=";
+ if (parent_ref.name) {
+ if (parent_ref.private_reference) {
+ std::cout << "*";
}
+ std::cout << parent_ref.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 (parent_ref.id) {
+ std::cout << parent_ref.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*> sorted_entries;
+ for (const auto& entry : type->entries) {
+ auto iter = std::lower_bound(
+ sorted_entries.begin(), sorted_entries.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;
+ }
+ });
+ sorted_entries.insert(iter, entry.get());
+ }
+
+ for (const ResourceEntry* entry : sorted_entries) {
+ 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->symbol_status.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.show_sources && !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);
+ CHECK(iter != names.end());
+ CHECK(*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& target_style) {
+ 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> styles_to_visit;
+ styles_to_visit.push(target_style);
+ for (; !styles_to_visit.empty(); styles_to_visit.pop()) {
+ const ResourceName& style_name = styles_to_visit.front();
+ std::set<ResourceName>& parents = graph[style_name];
+ if (!parents.empty()) {
+ // We've already visited this style.
+ continue;
+ }
+
+ Maybe<ResourceTable::SearchResult> result = table->FindResource(style_name);
+ 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());
+ styles_to_visit.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& style_name = entry.first;
+ size_t style_node_index = GetNodeIndex(names, style_name);
+
+ for (const auto& parent_name : entry.second) {
+ std::cout << " node_" << style_node_index << " -> "
+ << "node_" << GetNodeIndex(names, parent_name) << ";\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";
- }
+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";
}
+ }
- 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 << prefix_;
+ std::cerr << "E: ";
+ if (!el->namespace_uri.empty()) {
+ std::cerr << el->namespace_uri << ":";
+ }
+ std::cerr << el->name << " (line=" << el->line_number << ")\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 << prefix_ << " A: ";
+ if (!attr.namespace_uri.empty()) {
+ std::cerr << attr.namespace_uri << ":";
+ }
+ 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 previous_size = prefix_.size();
+ prefix_ += " ";
+ xml::Visitor::Visit(el);
+ prefix_.resize(previous_size);
+ }
- const size_t previousSize = mPrefix.size();
- mPrefix += " ";
- xml::Visitor::visit(ns);
- mPrefix.resize(previousSize);
- }
+ void Visit(xml::Namespace* ns) override {
+ std::cerr << prefix_;
+ std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri
+ << " (line=" << ns->line_number << ")\n";
- void visit(xml::Text* text) override {
- std::cerr << mPrefix;
- std::cerr << "T: '" << text->text << "'\n";
- }
+ const size_t previous_size = prefix_.size();
+ prefix_ += " ";
+ xml::Visitor::Visit(ns);
+ prefix_.resize(previous_size);
+ }
-private:
- std::string mPrefix;
+ void Visit(xml::Text* text) override {
+ std::cerr << prefix_;
+ std::cerr << "T: '" << text->text << "'\n";
+ }
+
+ private:
+ std::string prefix_;
};
-} // namespace
+} // namespace
-void Debug::dumpXml(xml::XmlResource* doc) {
- XmlPrinter printer;
- doc->root->accept(&printer);
+void Debug::DumpXml(xml::XmlResource* doc) {
+ XmlPrinter printer;
+ doc->root->Accept(&printer);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index c0fcbf1..56e2e95 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -17,27 +17,28 @@
#ifndef AAPT_DEBUG_H
#define AAPT_DEBUG_H
+// Include for printf-like debugging.
+#include <iostream>
+
#include "Resource.h"
#include "ResourceTable.h"
#include "xml/XmlDom.h"
-// Include for printf-like debugging.
-#include <iostream>
-
namespace aapt {
struct DebugPrintTableOptions {
- bool showSources = false;
+ bool show_sources = 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& target_style);
+ 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..5bc86a9 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -17,131 +17,125 @@
#ifndef AAPT_DIAGNOSTICS_H
#define AAPT_DIAGNOSTICS_H
-#include "Source.h"
-#include "util/StringPiece.h"
-#include "util/Util.h"
-
-#include <android-base/macros.h>
#include <iostream>
#include <sstream>
#include <string>
+#include "android-base/macros.h"
+
+#include "Source.h"
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
namespace aapt {
struct DiagMessageActual {
- Source source;
- std::string message;
+ Source source;
+ std::string message;
};
struct DiagMessage {
-private:
- Source mSource;
- std::stringstream mMessage;
+ public:
+ DiagMessage() = default;
-public:
- DiagMessage() = default;
+ explicit DiagMessage(const StringPiece& src) : source_(src) {}
- explicit DiagMessage(const StringPiece& src) : mSource(src) {
- }
+ explicit DiagMessage(const Source& src) : source_(src) {}
- explicit DiagMessage(const Source& src) : mSource(src) {
- }
+ explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) {}
- explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {
- }
+ template <typename T>
+ DiagMessage& operator<<(const T& value) {
+ message_ << value;
+ return *this;
+ }
- template <typename T>
- DiagMessage& operator<<(const T& value) {
- mMessage << value;
- return *this;
- }
+ DiagMessageActual Build() const {
+ return DiagMessageActual{source_, message_.str()};
+ }
- DiagMessageActual build() const {
- return DiagMessageActual{ mSource, mMessage.str() };
- }
+ private:
+ Source source_;
+ std::stringstream message_;
};
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& actual_msg) 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:
+ num_errors_++;
+ if (num_errors_ > 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 (!actual_msg.source.path.empty()) {
+ std::cerr << actual_msg.source << ": ";
+ }
+ std::cerr << tag << ": " << actual_msg.message << "." << std::endl;
+ }
- DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
+ private:
+ size_t num_errors_ = 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)
+ : source_(src), diag_(diag) {}
- void log(Level level, DiagMessageActual& actualMsg) override {
- actualMsg.source.path = mSource.path;
- mDiag->log(level, actualMsg);
- }
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ actual_msg.source.path = source_.path;
+ diag_->Log(level, actual_msg);
+ }
-private:
- Source mSource;
- IDiagnostics* mDiag;
+ private:
+ Source source_;
+ IDiagnostics* diag_;
- 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
new file mode 100644
index 0000000..118a385
--- /dev/null
+++ b/tools/aapt2/DominatorTree.cpp
@@ -0,0 +1,94 @@
+/*
+ * 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 "DominatorTree.h"
+
+#include <algorithm>
+
+#include "android-base/logging.h"
+
+#include "ConfigDescription.h"
+
+namespace aapt {
+
+DominatorTree::DominatorTree(
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
+ for (const auto& config : configs) {
+ product_roots_[config->product].TryAddChild(
+ util::make_unique<Node>(config.get(), nullptr));
+ }
+}
+
+void DominatorTree::Accept(Visitor* visitor) {
+ for (auto& entry : product_roots_) {
+ visitor->VisitTree(entry.first, &entry.second);
+ }
+}
+
+bool DominatorTree::Node::TryAddChild(std::unique_ptr<Node> new_child) {
+ CHECK(new_child->value_) << "cannot add a root or empty node as a child";
+ if (value_ && !Dominates(new_child.get())) {
+ // This is not the root and the child dominates us.
+ return false;
+ }
+ return AddChild(std::move(new_child));
+}
+
+bool DominatorTree::Node::AddChild(std::unique_ptr<Node> new_child) {
+ bool has_dominated_children = false;
+ // Demote children dominated by the new config.
+ for (auto& child : children_) {
+ if (new_child->Dominates(child.get())) {
+ child->parent_ = new_child.get();
+ new_child->children_.push_back(std::move(child));
+ child = {};
+ has_dominated_children = true;
+ }
+ }
+ // Remove dominated children.
+ if (has_dominated_children) {
+ children_.erase(
+ std::remove_if(children_.begin(), children_.end(),
+ [](const std::unique_ptr<Node>& child) -> bool {
+ return child == nullptr;
+ }),
+ children_.end());
+ }
+ // Add the new config to a child if a child dominates the new config.
+ for (auto& child : children_) {
+ if (child->Dominates(new_child.get())) {
+ child->AddChild(std::move(new_child));
+ return true;
+ }
+ }
+ // The new config is not dominated by a child, so add it here.
+ new_child->parent_ = this;
+ children_.push_back(std::move(new_child));
+ return true;
+}
+
+bool DominatorTree::Node::Dominates(const Node* other) const {
+ // Check root node dominations.
+ if (other->is_root_node()) {
+ return is_root_node();
+ } else if (is_root_node()) {
+ return true;
+ }
+ // Neither node is a root node; compare the configurations.
+ return value_->config.Dominates(other->value_->config);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/DominatorTree.h b/tools/aapt2/DominatorTree.h
new file mode 100644
index 0000000..7d50935
--- /dev/null
+++ b/tools/aapt2/DominatorTree.h
@@ -0,0 +1,121 @@
+/*
+ * 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_DOMINATOR_TREE_H
+#define AAPT_DOMINATOR_TREE_H
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ResourceTable.h"
+
+namespace aapt {
+
+/**
+ * A dominator tree of configurations as defined by resolution rules for Android
+ * resources.
+ *
+ * A node in the tree represents a resource configuration.
+ *
+ * The tree has the following property:
+ *
+ * Each child of a given configuration defines a strict superset of qualifiers
+ * and has a value that is at least as specific as that of its ancestors. A
+ * value is "at least as specific" if it is either identical or it represents a
+ * stronger requirement.
+ * For example, v21 is more specific than v11, and w1200dp is more specific than
+ * w800dp.
+ *
+ * The dominator tree relies on the underlying configurations passed to it. If
+ * the configurations passed to the dominator tree go out of scope, the tree
+ * will exhibit undefined behavior.
+ */
+class DominatorTree {
+ public:
+ explicit DominatorTree(
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& configs);
+
+ class Node {
+ public:
+ explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr)
+ : value_(value), parent_(parent) {}
+
+ inline ResourceConfigValue* value() const { return value_; }
+
+ inline Node* parent() const { return parent_; }
+
+ inline bool is_root_node() const { return !value_; }
+
+ inline const std::vector<std::unique_ptr<Node>>& children() const {
+ return children_;
+ }
+
+ bool TryAddChild(std::unique_ptr<Node> new_child);
+
+ private:
+ bool AddChild(std::unique_ptr<Node> new_child);
+ bool Dominates(const Node* other) const;
+
+ ResourceConfigValue* value_;
+ Node* parent_;
+ std::vector<std::unique_ptr<Node>> children_;
+
+ 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>& product_roots() const {
+ return product_roots_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DominatorTree);
+
+ std::map<std::string, Node> product_roots_;
+};
+
+} // namespace aapt
+
+#endif // AAPT_DOMINATOR_TREE_H
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
new file mode 100644
index 0000000..e89c6be
--- /dev/null
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -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.
+ */
+
+#include "DominatorTree.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "test/Test.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+namespace {
+
+class PrettyPrinter : public DominatorTree::Visitor {
+ public:
+ explicit PrettyPrinter(const int indent = 2) : indent_(indent) {}
+
+ void VisitTree(const std::string& product,
+ DominatorTree::Node* root) override {
+ for (auto& child : root->children()) {
+ VisitNode(child.get(), 0);
+ }
+ }
+
+ std::string ToString(DominatorTree* tree) {
+ buffer_.str("");
+ buffer_.clear();
+ tree->Accept(this);
+ return buffer_.str();
+ }
+
+ private:
+ void VisitConfig(const DominatorTree::Node* node, const int indent) {
+ auto config_string = node->value()->config.toString();
+ buffer_ << std::string(indent, ' ')
+ << (config_string.isEmpty() ? "<default>" : config_string)
+ << std::endl;
+ }
+
+ void VisitNode(const DominatorTree::Node* node, const int indent) {
+ VisitConfig(node, indent);
+ for (const auto& child : node->children()) {
+ VisitNode(child.get(), indent + indent_);
+ }
+ }
+
+ std::stringstream buffer_;
+ const int indent_ = 2;
+};
+
+} // namespace
+
+TEST(DominatorTreeTest, DefaultDominatesEverything) {
+ const ConfigDescription default_config = {};
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+ const ConfigDescription sw600dp_land_config =
+ test::ParseConfigOrDie("sw600dp-land-v13");
+
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
+
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
+
+ std::string expected =
+ "<default>\n"
+ " land\n"
+ " sw600dp-land-v13\n";
+ EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+TEST(DominatorTreeTest, ProductsAreDominatedSeparately) {
+ const ConfigDescription default_config = {};
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+ const ConfigDescription sw600dp_land_config =
+ test::ParseConfigOrDie("sw600dp-land-v13");
+
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(default_config, "phablet"));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet"));
+
+ DominatorTree tree(configs);
+ PrettyPrinter printer;
+
+ std::string expected =
+ "<default>\n"
+ " land\n"
+ "<default>\n"
+ " sw600dp-land-v13\n";
+ EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) {
+ const ConfigDescription default_config = {};
+ const ConfigDescription en_config = test::ParseConfigOrDie("en");
+ const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
+ const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4");
+ const ConfigDescription ldrtl_xhdpi_config =
+ test::ParseConfigOrDie("ldrtl-xhdpi-v4");
+ const ConfigDescription sw300dp_config =
+ test::ParseConfigOrDie("sw300dp-v13");
+ const ConfigDescription sw540dp_config =
+ test::ParseConfigOrDie("sw540dp-v14");
+ const ConfigDescription sw600dp_config =
+ test::ParseConfigOrDie("sw600dp-v14");
+ const ConfigDescription sw720dp_config =
+ test::ParseConfigOrDie("sw720dp-v13");
+ const ConfigDescription v20_config = test::ParseConfigOrDie("v20");
+
+ std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+ configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(en_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, ""));
+ configs.push_back(
+ util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(sw720dp_config, ""));
+ configs.push_back(util::make_unique<ResourceConfigValue>(v20_config, ""));
+
+ 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));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 3731ac7..c98cd37 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -15,165 +15,178 @@
*/
#include "Flags.h"
-#include "util/StringPiece.h"
-#include "util/Util.h"
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
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;
- };
-
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
- return *this;
-}
-
-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;
- };
-
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
- return *this;
-}
-
-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;
- };
-
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
- return *this;
-}
-
-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;
- };
-
- mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
- return *this;
-}
-
-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;
- };
-
- 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;
- };
-
- 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;
-
- *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";
- }
-
- // 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();
-}
-
-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 (const Flag& flag : mFlags) {
- if (flag.required && !flag.parsed) {
- *outError << "missing required flag " << flag.name << "\n\n";
- usage(command, outError);
- return false;
- }
- }
+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_.push_back(
+ Flag{name.ToString(), description.ToString(), func, true, 1, false});
+ return *this;
}
-const std::vector<std::string>& Flags::getArgs() {
- return mArgs;
+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;
+ };
+
+ flags_.push_back(
+ Flag{name.ToString(), description.ToString(), func, true, 1, false});
+ return *this;
}
-} // namespace aapt
+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;
+ };
+
+ flags_.push_back(
+ Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ return *this;
+}
+
+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;
+ };
+
+ flags_.push_back(
+ Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ return *this;
+}
+
+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;
+ };
+
+ flags_.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_.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;
+
+ *out << command << " [options]";
+ for (const Flag& flag : flags_) {
+ if (flag.required) {
+ *out << " " << flag.name << " arg";
+ }
+ }
+
+ *out << " files...\n\nOptions:\n";
+
+ for (const Flag& flag : flags_) {
+ std::string argline = flag.name;
+ if (flag.num_args > 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 = " ";
+ }
+ }
+ *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,
+ std::ostream* out_error) {
+ for (size_t i = 0; i < args.size(); i++) {
+ StringPiece arg = args[i];
+ if (*(arg.data()) != '-') {
+ args_.push_back(arg.ToString());
+ continue;
+ }
+
+ if (arg == "-h" || arg == "--help") {
+ Usage(command, out_error);
+ return false;
+ }
+
+ bool match = false;
+ for (Flag& flag : flags_) {
+ if (arg == flag.name) {
+ if (flag.num_args > 0) {
+ i++;
+ if (i >= args.size()) {
+ *out_error << flag.name << " missing argument.\n\n";
+ Usage(command, out_error);
+ return false;
+ }
+ flag.action(args[i]);
+ } else {
+ flag.action({});
+ }
+ flag.parsed = true;
+ match = true;
+ break;
+ }
+ }
+
+ if (!match) {
+ *out_error << "unknown option '" << arg << "'.\n\n";
+ Usage(command, out_error);
+ return false;
+ }
+ }
+
+ for (const Flag& flag : flags_) {
+ if (flag.required && !flag.parsed) {
+ *out_error << "missing required flag " << flag.name << "\n\n";
+ Usage(command, out_error);
+ return false;
+ }
+ }
+ return true;
+}
+
+const std::vector<std::string>& Flags::GetArgs() { return args_; }
+
+} // namespace aapt
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
index b092855..9feff6b 100644
--- a/tools/aapt2/Flags.h
+++ b/tools/aapt2/Flags.h
@@ -17,54 +17,57 @@
#ifndef AAPT_FLAGS_H
#define AAPT_FLAGS_H
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-
#include <functional>
#include <ostream>
#include <string>
#include <unordered_set>
#include <vector>
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
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 num_args;
- bool parsed;
- };
+ bool parsed;
+ };
- std::vector<Flag> mFlags;
- std::vector<std::string> mArgs;
+ std::vector<Flag> flags_;
+ std::vector<std::string> args_;
};
-} // 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..78f56c7a 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -15,237 +15,240 @@
*/
#include "Locale.h"
-#include "util/Util.h"
+
+#include <ctype.h>
#include <algorithm>
-#include <ctype.h>
#include <string>
#include <vector>
+#include "util/Util.h"
+
namespace aapt {
using android::ResTable_config;
-void LocaleValue::setLanguage(const char* languageChars) {
- size_t i = 0;
- while ((*languageChars) != '\0') {
- language[i++] = ::tolower(*languageChars);
- languageChars++;
- }
+void LocaleValue::set_language(const char* language_chars) {
+ size_t i = 0;
+ while ((*language_chars) != '\0') {
+ language[i++] = ::tolower(*language_chars);
+ language_chars++;
+ }
}
-void LocaleValue::setRegion(const char* regionChars) {
- size_t i = 0;
- while ((*regionChars) != '\0') {
- region[i++] = ::toupper(*regionChars);
- regionChars++;
- }
+void LocaleValue::set_region(const char* region_chars) {
+ size_t i = 0;
+ while ((*region_chars) != '\0') {
+ region[i++] = ::toupper(*region_chars);
+ region_chars++;
+ }
}
-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++;
- }
-}
-
-void LocaleValue::setVariant(const char* 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);
-}
-
-static inline bool isNumber(const std::string& str) {
- 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, '_');
-
- 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;
- }
-
- // 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;
- }
-
- // 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;
- }
-
- 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;
- }
-
- 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::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());
- setScript(subtags[1].c_str());
- setRegion(subtags[2].c_str());
- setVariant(subtags[3].c_str());
- } else {
- return -1;
- }
-
- ++iter;
-
+void LocaleValue::set_script(const char* script_chars) {
+ size_t i = 0;
+ while ((*script_chars) != '\0') {
+ if (i == 0) {
+ script[i++] = ::toupper(*script_chars);
} else {
- if ((part.length() == 2 || part.length() == 3)
- && isAlpha(part) && part != "car") {
- setLanguage(part.c_str());
- ++iter;
+ script[i++] = ::tolower(*script_chars);
+ }
+ script_chars++;
+ }
+}
- if (iter != end) {
- const std::string& regionPart = *iter;
- if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
- setRegion(regionPart.c_str() + 1);
- ++iter;
- }
- }
+void LocaleValue::set_variant(const char* variant_chars) {
+ size_t i = 0;
+ while ((*variant_chars) != '\0') {
+ variant[i++] = *variant_chars;
+ variant_chars++;
+ }
+}
+
+static inline bool is_alpha(const std::string& str) {
+ return std::all_of(std::begin(str), std::end(str), ::isalpha);
+}
+
+static inline bool is_number(const std::string& str) {
+ 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, '_');
+
+ const int num_tags = parts.size();
+ bool valid = false;
+ if (num_tags >= 1) {
+ const std::string& lang = parts[0];
+ if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
+ set_language(lang.c_str());
+ valid = true;
+ }
+ }
+
+ if (!valid || num_tags == 1) {
+ return valid;
+ }
+
+ // At this point, valid == true && numTags > 1.
+ const std::string& part2 = parts[1];
+ if ((part2.length() == 2 && is_alpha(part2)) ||
+ (part2.length() == 3 && is_number(part2))) {
+ set_region(part2.c_str());
+ } else if (part2.length() == 4 && is_alpha(part2)) {
+ set_script(part2.c_str());
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
+ set_variant(part2.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || num_tags == 2) {
+ return valid;
+ }
+
+ // At this point, valid == true && numTags > 1.
+ const std::string& part3 = parts[2];
+ if (((part3.length() == 2 && is_alpha(part3)) ||
+ (part3.length() == 3 && is_number(part3))) &&
+ script[0]) {
+ set_region(part3.c_str());
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
+ set_variant(part3.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || num_tags == 3) {
+ return valid;
+ }
+
+ const std::string& part4 = parts[3];
+ if (part4.length() >= 4 && part4.length() <= 8) {
+ set_variant(part4.c_str());
+ } else {
+ valid = false;
+ }
+
+ if (!valid || num_tags > 4) {
+ return false;
+ }
+
+ return true;
+}
+
+ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
+ std::vector<std::string>::iterator end) {
+ const std::vector<std::string>::iterator start_iter = 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) {
+ set_language(subtags[0].c_str());
+ } else if (subtags.size() == 2) {
+ set_language(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:
+ set_region(subtags[1].c_str());
+ break;
+ case 4:
+ if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+ // This is a variant: fall through
+ } else {
+ set_script(subtags[1].c_str());
+ break;
+ }
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ set_variant(subtags[1].c_str());
+ break;
+ default:
+ return -1;
+ }
+ } else if (subtags.size() == 3) {
+ // The language is always the first subtag.
+ set_language(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) {
+ set_script(subtags[1].c_str());
+ } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
+ set_region(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) {
+ set_variant(subtags[2].c_str());
+ } else {
+ set_region(subtags[2].c_str());
+ }
+ } else if (subtags.size() == 4) {
+ set_language(subtags[0].c_str());
+ set_script(subtags[1].c_str());
+ set_region(subtags[2].c_str());
+ set_variant(subtags[3].c_str());
+ } else {
+ return -1;
+ }
+
+ ++iter;
+
+ } else {
+ if ((part.length() == 2 || part.length() == 3) && is_alpha(part) &&
+ part != "car") {
+ set_language(part.c_str());
+ ++iter;
+
+ if (iter != end) {
+ const std::string& region_part = *iter;
+ if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
+ set_region(region_part.c_str() + 1);
+ ++iter;
}
+ }
}
+ }
- return static_cast<ssize_t>(iter - startIter);
+ return static_cast<ssize_t>(iter - start_iter);
}
-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));
- }
+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));
+ }
- 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);
+void LocaleValue::WriteTo(ResTable_config* out) const {
+ 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..fc6c448 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -17,98 +17,97 @@
#ifndef AAPT_LOCALE_VALUE_H
#define AAPT_LOCALE_VALUE_H
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
#include <string>
#include <vector>
+#include "androidfw/ResourceTypes.h"
+
+#include "util/StringPiece.h"
+
namespace aapt {
/**
* 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 set_language(const char* language);
+ void set_region(const char* language);
+ void set_script(const char* script);
+ void set_variant(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..68b4cae 100644
--- a/tools/aapt2/Locale_test.cpp
+++ b/tools/aapt2/Locale_test.cpp
@@ -15,68 +15,82 @@
*/
#include "Locale.h"
-#include "util/Util.h"
-#include <gtest/gtest.h>
#include <string>
+#include "gtest/gtest.h"
+
+#include "util/Util.h"
+
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..8dd176d 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#include "util/StringPiece.h"
-
#include <iostream>
#include <vector>
+#include "util/StringPiece.h"
+
namespace aapt {
// DO NOT UPDATE, this is more of a marketing version.
@@ -27,46 +27,47 @@
// Update minor version whenever a feature or flag is added.
static const char* sMinorVersion = "2";
-int printVersion() {
- std::cerr << "Android Asset Packaging Tool (aapt) "
- << sMajorVersion << "." << sMinorVersion << std::endl;
- return 0;
+int PrintVersion() {
+ std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
+ << sMinorVersion << std::endl;
+ return 0;
}
-extern int compile(const std::vector<StringPiece>& args);
-extern int link(const std::vector<StringPiece>& args);
-extern int dump(const std::vector<StringPiece>& args);
-extern int diff(const std::vector<StringPiece>& args);
+extern int Compile(const std::vector<StringPiece>& args);
+extern int Link(const std::vector<StringPiece>& args);
+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..dba2d09 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -17,78 +17,82 @@
#ifndef AAPT_NAME_MANGLER_H
#define AAPT_NAME_MANGLER_H
-#include "Resource.h"
-#include "util/Maybe.h"
-
#include <set>
#include <string>
+#include "Resource.h"
+#include "util/Maybe.h"
+
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 target_package_name;
- /**
- * 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> packages_to_mangle;
};
class NameMangler {
-private:
- NameManglerPolicy mPolicy;
+ public:
+ explicit NameMangler(NameManglerPolicy policy) : policy_(policy) {}
-public:
- explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
+ Maybe<ResourceName> MangleName(const ResourceName& name) {
+ if (policy_.target_package_name == name.package ||
+ policy_.packages_to_mangle.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 mangled_entry_name = MangleEntry(name.package, name.entry);
+ return ResourceName(policy_.target_package_name, name.type,
+ mangled_entry_name);
+ }
- 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() || policy_.target_package_name == package) {
+ return false;
+ }
+ return policy_.packages_to_mangle.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* out_name, std::string* out_package) {
+ size_t pivot = out_name->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;
- }
+ out_package->assign(out_name->data(), pivot);
+ out_name->assign(out_name->data() + pivot + 1,
+ out_name->size() - (pivot + 1));
+ return true;
+ }
- /**
- * 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;
- }
+ private:
+ NameManglerPolicy policy_;
};
-} // 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..bc89b5c 100644
--- a/tools/aapt2/NameMangler_test.cpp
+++ b/tools/aapt2/NameMangler_test.cpp
@@ -15,32 +15,33 @@
*/
#include "NameMangler.h"
-#include "test/Test.h"
#include <string>
+#include "test/Test.h"
+
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 mangled_name = NameMangler::MangleEntry(package, name);
+ EXPECT_EQ(mangled_name, "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 unmangled_package;
+ std::string unmangled_name = mangled_name;
+ ASSERT_TRUE(NameMangler::Unmangle(&unmangled_name, &unmangled_package));
+ EXPECT_EQ(unmangled_name, "Platform.AppCompat");
+ EXPECT_EQ(unmangled_package, "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..1d414743 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -15,82 +15,104 @@
*/
#include "Resource.h"
-#include "util/StringPiece.h"
#include <map>
#include <string>
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 {};
+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 {};
}
-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;
+const ResourceType* ParseResourceType(const StringPiece& str) {
+ 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..78acb70 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -17,12 +17,6 @@
#ifndef AAPT_RESOURCE_H
#define AAPT_RESOURCE_H
-#include "ConfigDescription.h"
-#include "Source.h"
-#include "util/StringPiece.h"
-
-#include <utils/JenkinsHash.h>
-
#include <iomanip>
#include <limits>
#include <sstream>
@@ -30,6 +24,12 @@
#include <tuple>
#include <vector>
+#include "utils/JenkinsHash.h"
+
+#include "ConfigDescription.h"
+#include "Source.h"
+#include "util/StringPiece.h"
+
namespace aapt {
/**
@@ -37,55 +37,55 @@
* 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);
+StringPiece ToString(ResourceType type);
/**
* Returns a pointer to a valid ResourceType, or nullptr if
* the string was invalid.
*/
-const ResourceType* parseResourceType(const StringPiece& str);
+const ResourceType* ParseResourceType(const StringPiece& str);
/**
* A resource's name. This can uniquely identify
* a resource in the ResourceTable.
*/
struct ResourceName {
- std::string package;
- ResourceType type;
- std::string entry;
+ std::string package;
+ ResourceType type = ResourceType::kRaw;
+ std::string entry;
- ResourceName() : type(ResourceType::kRaw) {}
- ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
+ ResourceName() = default;
+ 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 is_valid() 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 = ResourceType::kRaw;
+ 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 is_valid() 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 res_id); // 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 is_valid() const;
+ uint8_t package_id() const;
+ uint8_t type_id() const;
+ uint16_t entry_id() 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> exported_symbols;
};
/**
- * 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(uint32_t res_id) : id(res_id) {}
+
+inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
+ : id((p << 24) | (t << 16) | e) {}
+
+inline bool ResourceId::is_valid() const {
+ return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
}
-inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
+inline uint8_t ResourceId::package_id() const {
+ return static_cast<uint8_t>(id >> 24);
}
-inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
+inline uint8_t ResourceId::type_id() const {
+ return static_cast<uint8_t>(id >> 16);
}
-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;
-}
-
-inline uint8_t ResourceId::packageId() const {
- return static_cast<uint8_t>(id >> 24);
-}
-
-inline uint8_t ResourceId::typeId() const {
- return static_cast<uint8_t>(id >> 16);
-}
-
-inline uint16_t ResourceId::entryId() const {
- return static_cast<uint16_t>(id);
+inline uint16_t ResourceId::entry_id() const {
+ 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& res_id) {
+ std::ios_base::fmtflags old_flags = out.flags();
+ char old_fill = out.fill();
+ out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex
+ << res_id.id;
+ out.flags(old_flags);
+ out.fill(old_fill);
+ 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();
+inline bool ResourceName::is_valid() const {
+ 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();
+inline std::string ResourceName::ToString() const {
+ 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);
+inline ResourceName ResourceNameRef::ToResourceName() const {
+ return ResourceName(package, type, entry);
}
-inline bool ResourceNameRef::isValid() const {
- return !package.empty() && !entry.empty();
+inline bool ResourceNameRef::is_valid() const {
+ 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..b16def4 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -15,6 +15,12 @@
*/
#include "ResourceParser.h"
+
+#include <functional>
+#include <sstream>
+
+#include "android-base/logging.h"
+
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
@@ -23,450 +29,492 @@
#include "util/Util.h"
#include "xml/XmlPullParser.h"
-#include <functional>
-#include <sstream>
-
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;
+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;
}
-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;
+static uint32_t ParseFormatAttribute(const StringPiece& str) {
+ uint32_t mask = 0;
+ for (StringPiece part : util::Tokenize(str, '|')) {
+ StringPiece trimmed_part = util::TrimWhitespace(part);
+ uint32_t type = ParseFormatType(trimmed_part);
+ 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> symbol_state;
+ std::string comment;
+ std::unique_ptr<Value> value;
+ std::list<ParsedResource> child_resources;
};
// 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 trimmed_comment = util::TrimWhitespace(res->comment);
+ if (trimmed_comment.size() != res->comment.size()) {
+ // Only if there was a change do we re-assign.
+ res->comment = trimmed_comment.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->symbol_state) {
+ Symbol symbol;
+ symbol.state = res->symbol_state.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->child_resources) {
+ 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)
+ : diag_(diag),
+ table_(table),
+ source_(source),
+ config_(config),
+ options_(options) {}
/**
* Build a string from XML that converts nested elements into Span objects.
*/
-bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
- StyleString* outStyleString) {
- std::vector<Span> spanStack;
+bool ResourceParser::FlattenXmlSubtree(xml::XmlPullParser* parser,
+ std::string* out_raw_string,
+ StyleString* out_style_string) {
+ std::vector<Span> span_stack;
- 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;
+ out_raw_string->clear();
+ out_style_string->spans.clear();
+ util::StringBuilder builder;
+ size_t depth = 1;
+ while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
+ const xml::XmlPullParser::Event event = parser->event();
+ if (event == xml::XmlPullParser::Event::kEndElement) {
+ if (!parser->element_namespace().empty()) {
+ // We already warned and skipped the start element, so just skip here
+ // too
+ continue;
+ }
- depth--;
- if (depth == 0) {
- break;
- }
-
- 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::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);
- }
- }
- 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);
+ depth--;
+ if (depth == 0) {
break;
- };
+ }
- if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << "xml parser error: " << parser->getLastError());
- return false;
+ span_stack.back().last_char = builder.Utf16Len() - 1;
+ out_style_string->spans.push_back(span_stack.back());
+ span_stack.pop_back();
+
+ } else if (event == xml::XmlPullParser::Event::kText) {
+ out_raw_string->append(parser->text());
+ builder.Append(parser->text());
+
+ } else if (event == xml::XmlPullParser::Event::kStartElement) {
+ if (!parser->element_namespace().empty()) {
+ if (parser->element_namespace() != sXliffNamespaceUri) {
+ // Only warn if this isn't an xliff namespace.
+ diag_->Warn(DiagMessage(source_.WithLine(parser->line_number()))
+ << "skipping element '" << parser->element_name()
+ << "' with unknown namespace '"
+ << parser->element_namespace() << "'");
+ }
+ continue;
+ }
+ depth++;
+
+ // Build a span object out of the nested element.
+ std::string span_name = parser->element_name();
+ const auto end_attr_iter = parser->end_attributes();
+ for (auto attr_iter = parser->begin_attributes();
+ attr_iter != end_attr_iter; ++attr_iter) {
+ span_name += ";";
+ span_name += attr_iter->name;
+ span_name += "=";
+ span_name += attr_iter->value;
+ }
+
+ if (builder.Utf16Len() > std::numeric_limits<uint32_t>::max()) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "style string '" << builder.ToString()
+ << "' is too long");
+ error = true;
+ } else {
+ span_stack.push_back(
+ Span{span_name, static_cast<uint32_t>(builder.Utf16Len())});
+ }
+
+ } else if (event == xml::XmlPullParser::Event::kComment) {
+ // Skip
+ } else {
+ LOG(FATAL) << "unhandled XML event";
}
- return !error;
+ }
+ CHECK(span_stack.empty()) << "spans haven't been fully processed";
+
+ out_style_string->str = builder.ToString();
+ return !error;
}
-bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
- 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 ResourceParser::Parse(xml::XmlPullParser* parser) {
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip comments and text.
+ 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 (!parser->element_namespace().empty() ||
+ parser->element_name() != "resources") {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "root element must be <resources>");
+ return false;
}
- return !error;
-}
+ error |= !ParseResources(parser);
+ break;
+ };
-
-bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
- struct ItemTypeFormat {
- ResourceType type;
- uint32_t format;
- };
-
- 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 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();
-
- // 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;
- }
- }
- }
-
- // 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() << "'");
+ if (parser->event() == xml::XmlPullParser::Event::kBadDocument) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "xml parser error: " << parser->error());
return false;
+ }
+ return !error;
}
-bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
- const uint32_t format) {
- if (format == android::ResTable_map::TYPE_STRING) {
- return parseString(parser, outResource);
+bool ResourceParser::ParseResources(xml::XmlPullParser* parser) {
+ std::set<ResourceName> stripped_resources;
+
+ bool error = false;
+ std::string comment;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ const xml::XmlPullParser::Event event = parser->event();
+ if (event == xml::XmlPullParser::Event::kComment) {
+ comment = parser->comment();
+ continue;
}
- outResource->value = parseXml(parser, format, kNoRawString);
- if (!outResource->value) {
- mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type);
+ if (event == xml::XmlPullParser::Event::kText) {
+ if (!util::TrimWhitespace(parser->text()).empty()) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "plain text not allowed here");
+ error = true;
+ }
+ continue;
+ }
+
+ CHECK(event == xml::XmlPullParser::Event::kStartElement);
+
+ if (!parser->element_namespace().empty()) {
+ // Skip unknown namespace.
+ continue;
+ }
+
+ std::string element_name = parser->element_name();
+ if (element_name == "skip" || element_name == "eat-comment") {
+ comment = "";
+ continue;
+ }
+
+ ParsedResource parsed_resource;
+ parsed_resource.config = config_;
+ parsed_resource.source = source_.WithLine(parser->line_number());
+ parsed_resource.comment = std::move(comment);
+
+ // Extract the product name if it exists.
+ if (Maybe<StringPiece> maybe_product =
+ xml::FindNonEmptyAttribute(parser, "product")) {
+ parsed_resource.product = maybe_product.value().ToString();
+ }
+
+ // Parse the resource regardless of product.
+ if (!ParseResource(parser, &parsed_resource)) {
+ error = true;
+ continue;
+ }
+
+ if (!AddResourcesToTable(table_, diag_, &parsed_resource)) {
+ error = true;
+ }
+ }
+
+ // Check that we included at least one variant of each stripped resource.
+ for (const ResourceName& stripped_resource : stripped_resources) {
+ if (!table_->FindResource(stripped_resource)) {
+ // Failed to find the resource.
+ diag_->Error(DiagMessage(source_)
+ << "resource '" << stripped_resource
+ << "' "
+ "was filtered out but no product variant remains");
+ error = true;
+ }
+ }
+
+ return !error;
+}
+
+bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ struct ItemTypeFormat {
+ ResourceType type;
+ uint32_t format;
+ };
+
+ 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 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 resource_type = parser->element_name();
+
+ // The value format accepted for this resource.
+ uint32_t resource_format = 0u;
+
+ if (resource_type == "item") {
+ // Items have their type encoded in the type attribute.
+ if (Maybe<StringPiece> maybe_type =
+ xml::FindNonEmptyAttribute(parser, "type")) {
+ resource_type = maybe_type.value().ToString();
+ } else {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "<item> must have a 'type' attribute");
+ return false;
+ }
+
+ if (Maybe<StringPiece> maybe_format =
+ 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.
+ resource_format = ParseFormatType(maybe_format.value());
+ if (!resource_format) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "'" << maybe_format.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> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+
+ if (resource_type == "id") {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ out_resource->name.type = ResourceType::kId;
+ out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->value = util::make_unique<Id>();
+ return true;
+ }
+
+ const auto item_iter = elToItemMap.find(resource_type);
+ if (item_iter != elToItemMap.end()) {
+ // This is an item, record its type and format and start parsing.
+
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ out_resource->name.type = item_iter->second.type;
+ out_resource->name.entry = maybe_name.value().ToString();
+
+ // Only use the implicit format for this type if it wasn't overridden.
+ if (!resource_format) {
+ resource_format = item_iter->second.format;
+ }
+
+ if (!ParseItem(parser, out_resource, resource_format)) {
+ return false;
}
return true;
+ }
+
+ // This might be a bag or something.
+ const auto bag_iter = elToBagMap.find(resource_type);
+ if (bag_iter != elToBagMap.end()) {
+ // Ensure we have a name (unless this is a <public-group>).
+ if (resource_type != "public-group") {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ out_resource->name.entry = maybe_name.value().ToString();
+ }
+
+ // Call the associated parse method. The type will be filled in by the
+ // parse func.
+ if (!bag_iter->second(this, parser, out_resource)) {
+ 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* parsed_type = ParseResourceType(resource_type);
+ if (parsed_type) {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> missing 'name' attribute");
+ return false;
+ }
+
+ out_resource->name.type = *parsed_type;
+ out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->value =
+ ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
+ if (!out_resource->value) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for type '" << *parsed_type
+ << "'. Expected a reference");
+ return false;
+ }
+ return true;
+ }
+
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "unknown resource type '" << parser->element_name() << "'");
+ return false;
+}
+
+bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
+ ParsedResource* out_resource,
+ const uint32_t format) {
+ if (format == android::ResTable_map::TYPE_STRING) {
+ return ParseString(parser, out_resource);
+ }
+
+ out_resource->value = ParseXml(parser, format, kNoRawString);
+ if (!out_resource->value) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid "
+ << out_resource->name.type);
+ return false;
+ }
+ return true;
}
/**
@@ -476,771 +524,839 @@
* 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,
- const bool allowRawValue) {
- const size_t beginXmlLine = parser->getLineNumber();
+std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
+ const uint32_t type_mask,
+ const bool allow_raw_value) {
+ const size_t begin_xml_line = parser->line_number();
- 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 raw_value;
+ StyleString style_string;
+ if (!FlattenXmlSubtree(parser, &raw_value, &style_string)) {
return {};
+ }
+
+ if (!style_string.spans.empty()) {
+ // This can only be a StyledString.
+ return util::make_unique<StyledString>(table_->string_pool.MakeRef(
+ style_string,
+ StringPool::Context(StringPool::Context::kStylePriority, config_)));
+ }
+
+ auto on_create_reference = [&](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(source_.WithLine(begin_xml_line));
+ table_->AddResource(name, {}, {}, std::move(id), diag_);
+ };
+
+ // Process the raw value.
+ std::unique_ptr<Item> processed_item =
+ ResourceUtils::TryParseItemForAttribute(raw_value, type_mask,
+ on_create_reference);
+ if (processed_item) {
+ // Fix up the reference.
+ if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
+ TransformReferenceFromNamespace(parser, "", ref);
+ }
+ return processed_item;
+ }
+
+ // Try making a regular string.
+ if (type_mask & android::ResTable_map::TYPE_STRING) {
+ // Use the trimmed, escaped string.
+ return util::make_unique<String>(table_->string_pool.MakeRef(
+ style_string.str, StringPool::Context(config_)));
+ }
+
+ if (allow_raw_value) {
+ // We can't parse this so return a RawString if we are allowed.
+ return util::make_unique<RawString>(
+ table_->string_pool.MakeRef(raw_value, StringPool::Context(config_)));
+ }
+ 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* out_resource) {
+ bool formatted = true;
+ if (Maybe<StringPiece> formatted_attr =
+ xml::FindAttribute(parser, "formatted")) {
+ Maybe<bool> maybe_formatted =
+ ResourceUtils::ParseBool(formatted_attr.value());
+ if (!maybe_formatted) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for 'formatted'. Must be a boolean");
+ return false;
}
+ formatted = maybe_formatted.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 = options_.translatable;
+ if (Maybe<StringPiece> translateable_attr =
+ xml::FindAttribute(parser, "translatable")) {
+ Maybe<bool> maybe_translateable =
+ ResourceUtils::ParseBool(translateable_attr.value());
+ if (!maybe_translateable) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
}
+ translateable = maybe_translateable.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;
- }
-
- 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);
- }
- }
-
- } 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;
- }
-
- 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;
-
- 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 (!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;
- }
-
- 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;
- }
+ out_resource->value =
+ ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
+ if (!out_resource->value) {
+ diag_->Error(DiagMessage(out_resource->source) << "not a valid string");
return false;
+ }
+
+ if (String* string_value = ValueCast<String>(out_resource->value.get())) {
+ string_value->SetTranslateable(translateable);
+
+ if (formatted && translateable) {
+ if (!util::VerifyJavaStringFormat(*string_value->value)) {
+ DiagMessage msg(out_resource->source);
+ msg << "multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?";
+ if (options_.error_on_positional_arguments) {
+ diag_->Error(msg);
+ return false;
+ }
+
+ diag_->Warn(msg);
+ }
+ }
+
+ } else if (StyledString* string_value =
+ ValueCast<StyledString>(out_resource->value.get())) {
+ string_value->SetTranslateable(translateable);
+ }
+ return true;
}
-bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) {
- if (parseSymbolImpl(parser, outResource)) {
- outResource->symbolState = SymbolState::kUndefined;
- return true;
- }
+bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public> must have a 'type' attribute");
return false;
+ }
+
+ const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
+ if (!parsed_type) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
+ << maybe_type.value()
+ << "' in <public>");
+ return false;
+ }
+
+ out_resource->name.type = *parsed_type;
+
+ if (Maybe<StringPiece> maybe_id_str =
+ xml::FindNonEmptyAttribute(parser, "id")) {
+ Maybe<ResourceId> maybe_id =
+ ResourceUtils::ParseResourceId(maybe_id_str.value());
+ if (!maybe_id) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
+ << maybe_id.value()
+ << "' in <public>");
+ return false;
+ }
+ out_resource->id = maybe_id.value();
+ }
+
+ if (*parsed_type == ResourceType::kId) {
+ // An ID marked as public is also the definition of an ID.
+ out_resource->value = util::make_unique<Id>();
+ }
+
+ out_resource->symbol_state = SymbolState::kPublic;
+ return true;
}
+bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public-group> must have a 'type' attribute");
+ return false;
+ }
-bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
- return parseAttrImpl(parser, outResource, false);
+ const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
+ if (!parsed_type) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
+ << maybe_type.value()
+ << "' in <public-group>");
+ return false;
+ }
+
+ Maybe<StringPiece> maybe_id_str =
+ xml::FindNonEmptyAttribute(parser, "first-id");
+ if (!maybe_id_str) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public-group> must have a 'first-id' attribute");
+ return false;
+ }
+
+ Maybe<ResourceId> maybe_id =
+ ResourceUtils::ParseResourceId(maybe_id_str.value());
+ if (!maybe_id) {
+ diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
+ << maybe_id_str.value()
+ << "' in <public-group>");
+ return false;
+ }
+
+ ResourceId next_id = maybe_id.value();
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() == xml::XmlPullParser::Event::kComment) {
+ comment = util::TrimWhitespace(parser->comment()).ToString();
+ continue;
+ } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "public") {
+ Maybe<StringPiece> maybe_name =
+ xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(item_source)
+ << "<public> must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ if (xml::FindNonEmptyAttribute(parser, "id")) {
+ diag_->Error(DiagMessage(item_source)
+ << "'id' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ if (xml::FindNonEmptyAttribute(parser, "type")) {
+ diag_->Error(DiagMessage(item_source)
+ << "'type' is ignored within <public-group>");
+ error = true;
+ continue;
+ }
+
+ ParsedResource child_resource;
+ child_resource.name.type = *parsed_type;
+ child_resource.name.entry = maybe_name.value().ToString();
+ child_resource.id = next_id;
+ child_resource.comment = std::move(comment);
+ child_resource.source = item_source;
+ child_resource.symbol_state = SymbolState::kPublic;
+ out_resource->child_resources.push_back(std::move(child_resource));
+
+ next_id.id += 1;
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
+ error = true;
+ }
+ }
+ return !error;
}
-bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
- bool weak) {
- outResource->name.type = ResourceType::kAttr;
+bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name()
+ << "> must have a 'type' attribute");
+ return false;
+ }
- // 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();
- }
+ const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
+ if (!parsed_type) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid resource type '" << maybe_type.value() << "' in <"
+ << parser->element_name() << ">");
+ return false;
+ }
- uint32_t typeMask = 0;
+ out_resource->name.type = *parsed_type;
+ return true;
+}
- 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* out_resource) {
+ if (ParseSymbolImpl(parser, out_resource)) {
+ out_resource->symbol_state = SymbolState::kPrivate;
return true;
+ }
+ return false;
}
-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());
-
- 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) });
+bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ if (ParseSymbolImpl(parser, out_resource)) {
+ out_resource->symbol_state = SymbolState::kUndefined;
return true;
+ }
+ return false;
}
-bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
- outResource->name.type = ResourceType::kStyle;
+bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseAttrImpl(parser, out_resource, false);
+}
- std::unique_ptr<Style> style = util::make_unique<Style>();
+bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource, bool weak) {
+ out_resource->name.type = ResourceType::kAttr;
- 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;
- }
+ // Attributes only end up in default configuration.
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config
+ << "' for attribute " << out_resource->name);
+ out_resource->config = ConfigDescription::DefaultConfig();
+ }
- // Transform the namespace prefix to the actual package name, and mark the reference as
- // private if appropriate.
- transformReferenceFromNamespace(parser, "", &style->parent.value());
- }
+ uint32_t type_mask = 0;
- } 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)));
- }
+ Maybe<StringPiece> maybe_format = xml::FindAttribute(parser, "format");
+ if (maybe_format) {
+ type_mask = ParseFormatAttribute(maybe_format.value());
+ if (type_mask == 0) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "invalid attribute format '" << maybe_format.value()
+ << "'");
+ return false;
+ }
+ }
+
+ Maybe<int32_t> maybe_min, maybe_max;
+
+ if (Maybe<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) {
+ StringPiece min_str = util::TrimWhitespace(maybe_min_str.value());
+ if (!min_str.empty()) {
+ std::u16string min_str16 = util::Utf8ToUtf16(min_str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(),
+ &value)) {
+ maybe_min = static_cast<int32_t>(value.data);
+ }
}
- 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 (!maybe_min) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "invalid 'min' value '" << min_str << "'");
+ return false;
+ }
+ }
- 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 (Maybe<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) {
+ StringPiece max_str = util::TrimWhitespace(maybe_max_str.value());
+ if (!max_str.empty()) {
+ std::u16string max_str16 = util::Utf8ToUtf16(max_str);
+ android::Res_value value;
+ if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(),
+ &value)) {
+ maybe_max = static_cast<int32_t>(value.data);
+ }
}
- if (error) {
+ if (!maybe_max) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "invalid 'max' value '" << max_str << "'");
+ return false;
+ }
+ }
+
+ if ((maybe_min || maybe_max) &&
+ (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "'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->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() == xml::XmlPullParser::Event::kComment) {
+ comment = util::TrimWhitespace(parser->comment()).ToString();
+ continue;
+ } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text.
+ continue;
+ }
+
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() &&
+ (element_name == "flag" || element_name == "enum")) {
+ if (element_name == "enum") {
+ if (type_mask & android::ResTable_map::TYPE_FLAGS) {
+ diag_->Error(DiagMessage(item_source)
+ << "can not define an <enum>; already defined a <flag>");
+ error = true;
+ continue;
+ }
+ type_mask |= android::ResTable_map::TYPE_ENUM;
+
+ } else if (element_name == "flag") {
+ if (type_mask & android::ResTable_map::TYPE_ENUM) {
+ diag_->Error(DiagMessage(item_source)
+ << "can not define a <flag>; already defined an <enum>");
+ error = true;
+ continue;
+ }
+ type_mask |= android::ResTable_map::TYPE_FLAGS;
+ }
+
+ if (Maybe<Attribute::Symbol> s =
+ ParseEnumOrFlagItem(parser, element_name)) {
+ Attribute::Symbol& symbol = s.value();
+ ParsedResource child_resource;
+ child_resource.name = symbol.symbol.name.value();
+ child_resource.source = item_source;
+ child_resource.value = util::make_unique<Id>();
+ out_resource->child_resources.push_back(std::move(child_resource));
+
+ symbol.symbol.SetComment(std::move(comment));
+ symbol.symbol.SetSource(item_source);
+
+ auto insert_result = items.insert(std::move(symbol));
+ if (!insert_result.second) {
+ const Attribute::Symbol& existing_symbol = *insert_result.first;
+ diag_->Error(DiagMessage(item_source)
+ << "duplicate symbol '"
+ << existing_symbol.symbol.name.value().entry << "'");
+
+ diag_->Note(DiagMessage(existing_symbol.symbol.GetSource())
+ << "first defined here");
+ error = true;
+ }
+ } else {
+ error = true;
+ }
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
+ 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->type_mask =
+ type_mask ? type_mask : uint32_t(android::ResTable_map::TYPE_ANY);
+ if (maybe_min) {
+ attr->min_int = maybe_min.value();
+ }
+
+ if (maybe_max) {
+ attr->max_int = maybe_max.value();
+ }
+ out_resource->value = std::move(attr);
+ return true;
+}
+
+Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(
+ xml::XmlPullParser* parser, const StringPiece& tag) {
+ const Source source = source_.WithLine(parser->line_number());
+
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <"
+ << tag << ">");
+ return {};
+ }
+
+ Maybe<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value");
+ if (!maybe_value) {
+ diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <"
+ << tag << ">");
+ return {};
+ }
+
+ std::u16string value16 = util::Utf8ToUtf16(maybe_value.value());
+ android::Res_value val;
+ if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
+ diag_->Error(DiagMessage(source) << "invalid value '" << maybe_value.value()
+ << "' for <" << tag
+ << ">; must be an integer");
+ return {};
+ }
+
+ return Attribute::Symbol{
+ Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())),
+ val.data};
+}
+
+bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
+ const Source source = source_.WithLine(parser->line_number());
+
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute");
+ return false;
+ }
+
+ Maybe<Reference> maybe_key =
+ ResourceUtils::ParseXmlAttributeName(maybe_name.value());
+ if (!maybe_key) {
+ diag_->Error(DiagMessage(source) << "invalid attribute name '"
+ << maybe_name.value() << "'");
+ return false;
+ }
+
+ TransformReferenceFromNamespace(parser, "", &maybe_key.value());
+ maybe_key.value().SetSource(source);
+
+ std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
+ if (!value) {
+ diag_->Error(DiagMessage(source) << "could not parse style item");
+ return false;
+ }
+
+ style->entries.push_back(
+ Style::Entry{std::move(maybe_key.value()), std::move(value)});
+ return true;
+}
+
+bool ResourceParser::ParseStyle(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ out_resource->name.type = ResourceType::kStyle;
+
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+
+ Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent");
+ if (maybe_parent) {
+ // If the parent is empty, we don't have a parent, but we also don't infer
+ // either.
+ if (!maybe_parent.value().empty()) {
+ std::string err_str;
+ style->parent = ResourceUtils::ParseStyleParentReference(
+ maybe_parent.value(), &err_str);
+ if (!style->parent) {
+ diag_->Error(DiagMessage(out_resource->source) << err_str);
return false;
+ }
+
+ // Transform the namespace prefix to the actual package name, and mark the
+ // reference as
+ // private if appropriate.
+ TransformReferenceFromNamespace(parser, "", &style->parent.value());
}
- outResource->value = std::move(style);
- return true;
+ } else {
+ // No parent was specified, so try inferring it from the style name.
+ std::string style_name = out_resource->name.entry;
+ size_t pos = style_name.find_last_of(u'.');
+ if (pos != std::string::npos) {
+ style->parent_inferred = true;
+ style->parent = Reference(
+ ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos)));
+ }
+ }
+
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
+ }
+
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace == "" && element_name == "item") {
+ error |= !ParseStyleItem(parser, style.get());
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << ":" << element_name << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ out_resource->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* out_resource) {
+ return ParseArrayImpl(parser, out_resource, 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* out_resource) {
+ return ParseArrayImpl(parser, out_resource,
+ 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* out_resource) {
+ return ParseArrayImpl(parser, out_resource,
+ android::ResTable_map::TYPE_STRING);
}
-bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource,
const uint32_t typeMask) {
- outResource->name.type = ResourceType::kArray;
+ out_resource->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 = options_.translatable;
+ if (Maybe<StringPiece> translateable_attr =
+ xml::FindAttribute(parser, "translatable")) {
+ Maybe<bool> maybe_translateable =
+ ResourceUtils::ParseBool(translateable_attr.value());
+ if (!maybe_translateable) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
}
- array->setTranslateable(translateable);
+ translateable = maybe_translateable.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->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
}
- if (error) {
- return false;
- }
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "item") {
+ std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
+ if (!item) {
+ diag_->Error(DiagMessage(item_source) << "could not parse array item");
+ error = true;
+ continue;
+ }
+ item->SetSource(item_source);
+ array->items.emplace_back(std::move(item));
- outResource->value = std::move(array);
- return true;
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "unknown tag <" << element_namespace << ":"
+ << element_name << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ out_resource->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* out_resource) {
+ out_resource->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->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Skip text and comments.
+ continue;
}
- if (error) {
- return false;
- }
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "item") {
+ Maybe<StringPiece> maybe_quantity =
+ xml::FindNonEmptyAttribute(parser, "quantity");
+ if (!maybe_quantity) {
+ diag_->Error(DiagMessage(item_source)
+ << "<item> in <plurals> requires attribute "
+ << "'quantity'");
+ error = true;
+ continue;
+ }
- outResource->value = std::move(plural);
- return true;
+ StringPiece trimmed_quantity =
+ util::TrimWhitespace(maybe_quantity.value());
+ size_t index = 0;
+ if (trimmed_quantity == "zero") {
+ index = Plural::Zero;
+ } else if (trimmed_quantity == "one") {
+ index = Plural::One;
+ } else if (trimmed_quantity == "two") {
+ index = Plural::Two;
+ } else if (trimmed_quantity == "few") {
+ index = Plural::Few;
+ } else if (trimmed_quantity == "many") {
+ index = Plural::Many;
+ } else if (trimmed_quantity == "other") {
+ index = Plural::Other;
+ } else {
+ diag_->Error(DiagMessage(item_source)
+ << "<item> in <plural> has invalid value '"
+ << trimmed_quantity << "' for attribute 'quantity'");
+ error = true;
+ continue;
+ }
+
+ if (plural->values[index]) {
+ diag_->Error(DiagMessage(item_source) << "duplicate quantity '"
+ << trimmed_quantity << "'");
+ error = true;
+ continue;
+ }
+
+ if (!(plural->values[index] = ParseXml(
+ parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
+ error = true;
+ }
+ plural->values[index]->SetSource(item_source);
+
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << "unknown tag <"
+ << element_namespace << ":"
+ << element_name << ">");
+ error = true;
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+
+ out_resource->value = std::move(plural);
+ return true;
}
-bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
- ParsedResource* outResource) {
- outResource->name.type = ResourceType::kStyleable;
+bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ out_resource->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.
+ out_resource->symbol_state = 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 (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config
+ << "' for styleable " << out_resource->name.entry);
+ out_resource->config = ConfigDescription::DefaultConfig();
+ }
+
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+
+ std::string comment;
+ bool error = false;
+ const size_t depth = parser->depth();
+ while (xml::XmlPullParser::NextChildNode(parser, depth)) {
+ if (parser->event() == xml::XmlPullParser::Event::kComment) {
+ comment = util::TrimWhitespace(parser->comment()).ToString();
+ continue;
+ } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
+ // Ignore text.
+ continue;
}
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ const Source item_source = source_.WithLine(parser->line_number());
+ const std::string& element_namespace = parser->element_namespace();
+ const std::string& element_name = parser->element_name();
+ if (element_namespace.empty() && element_name == "attr") {
+ Maybe<StringPiece> maybe_name =
+ xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(item_source)
+ << "<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> maybe_ref =
+ ResourceUtils::ParseXmlAttributeName(maybe_name.value());
+ if (!maybe_ref) {
+ diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '"
+ << maybe_name.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& child_ref = maybe_ref.value();
+ xml::TransformReferenceFromNamespace(parser, "", &child_ref);
- // 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 child_resource;
+ child_resource.name = child_ref.name.value();
+ child_resource.source = item_source;
+ child_resource.comment = std::move(comment);
- Reference& childRef = maybeRef.value();
- xml::transformReferenceFromNamespace(parser, "", &childRef);
+ if (!ParseAttrImpl(parser, &child_resource, 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.
+ child_ref.SetComment(child_resource.comment);
+ child_ref.SetSource(item_source);
+ styleable->entries.push_back(std::move(child_ref));
- if (!parseAttrImpl(parser, &childResource, true)) {
- error = true;
- continue;
- }
+ out_resource->child_resources.push_back(std::move(child_resource));
- // 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(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << "unknown tag <"
+ << element_namespace << ":"
+ << element_name << ">");
+ error = true;
}
- if (error) {
- return false;
- }
+ comment = {};
+ }
- outResource->value = std::move(styleable);
- return true;
+ if (error) {
+ return false;
+ }
+
+ out_resource->value = std::move(styleable);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index ece3090..11b1e5b 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -17,6 +17,10 @@
#ifndef AAPT_RESOURCE_PARSER_H
#define AAPT_RESOURCE_PARSER_H
+#include <memory>
+
+#include "android-base/macros.h"
+
#include "ConfigDescription.h"
#include "Diagnostics.h"
#include "ResourceTable.h"
@@ -26,86 +30,98 @@
#include "util/StringPiece.h"
#include "xml/XmlPullParser.h"
-#include <memory>
-
namespace aapt {
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 error_on_positional_arguments = 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 = {});
+ bool Parse(xml::XmlPullParser* parser);
- ResourceParser(const ResourceParser&) = delete; // No copy.
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceParser);
- bool parse(xml::XmlPullParser* parser);
+ /*
+ * Parses the XML subtree as a StyleString (flattened XML representation for
+ * strings
+ * with formatting). If successful, `out_style_string`
+ * contains the escaped and whitespace trimmed text, while `out_raw_string`
+ * contains the unescaped text. Returns true on success.
+ */
+ bool FlattenXmlSubtree(xml::XmlPullParser* parser,
+ std::string* out_raw_string,
+ StyleString* out_style_string);
-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 `type_mask`.
+ * If `allow_raw_value` 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 type_mask,
+ const bool allow_raw_value);
- /*
- * 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* out_resource);
- bool parseResources(xml::XmlPullParser* parser);
- bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ uint32_t format);
+ bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);
- bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format);
- bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParsePublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseSymbolImpl(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseAddResource(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ bool weak);
+ Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece& tag);
+ bool ParseStyle(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
+ bool ParseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseIntegerArray(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseStringArray(xml::XmlPullParser* parser,
+ ParsedResource* out_resource);
+ bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ uint32_t typeMask);
+ bool ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource);
- 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* diag_;
+ ResourceTable* table_;
+ Source source_;
+ ConfigDescription config_;
+ ResourceParserOptions options_;
};
-} // 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..2463911 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -15,523 +15,573 @@
*/
#include "ResourceParser.h"
+
+#include <sstream>
+#include <string>
+
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "test/Test.h"
#include "xml/XmlPullParser.h"
-#include <sstream>
-#include <string>
-
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 xml_parser(input);
+ ASSERT_FALSE(parser.Parse(&xml_parser));
}
-struct ResourceParserTest : public ::testing::Test {
- ResourceTable mTable;
- std::unique_ptr<IAaptContext> mContext;
+class ResourceParserTest : public ::testing::Test {
+ public:
+ void SetUp() override { context_ = 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(context_->GetDiagnostics(), &table_, 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();
- }
+ protected:
+ ResourceTable table_;
+ std::unique_ptr<IAaptContext> context_;
};
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>(&table_, "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>(&table_, "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>(&table_, "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 expected_str = "This is my aunt\u2019s string";
+ EXPECT_EQ(expected_str, *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].first_char);
+ EXPECT_EQ(23u, str->value->spans[0].last_char);
}
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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
- 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>(&table_, "attr/bar");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask);
}
-// 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 watch_config = 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, watch_config));
- 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>(&table_, "attr/foo",
+ watch_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz",
+ watch_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(
+ &table_, "styleable/bar", watch_config));
- 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>(&table_, "attr/foo"));
+ EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz"));
+ EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "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>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask);
+ EXPECT_EQ(10, attr->min_int);
+ EXPECT_EQ(23, attr->max_int);
}
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>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
}
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>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask);
}
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* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(enum_attr, nullptr);
+ EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM);
+ ASSERT_EQ(enum_attr->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(enum_attr->symbols[0].symbol.name);
+ EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar");
+ EXPECT_EQ(enum_attr->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(enum_attr->symbols[1].symbol.name);
+ EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat");
+ EXPECT_EQ(enum_attr->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(enum_attr->symbols[2].symbol.name);
+ EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz");
+ EXPECT_EQ(enum_attr->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* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo");
+ ASSERT_NE(nullptr, flag_attr);
+ EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS);
+ ASSERT_EQ(flag_attr->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(flag_attr->symbols[0].symbol.name);
+ EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar");
+ EXPECT_EQ(flag_attr->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(flag_attr->symbols[1].symbol.name);
+ EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat");
+ EXPECT_EQ(flag_attr->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(flag_attr->symbols[2].symbol.name);
+ EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz");
+ EXPECT_EQ(flag_attr->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> flag_value =
+ ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat");
+ ASSERT_NE(nullptr, flag_value);
+ EXPECT_EQ(flag_value->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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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->parent_inferred);
}
-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>(&table_, "style/foo.bar");
+ ASSERT_NE(nullptr, style);
+ AAPT_EXPECT_FALSE(style->parent);
+ EXPECT_FALSE(style->parent_inferred);
}
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>(&table_, "style/foo");
+ ASSERT_NE(nullptr, style);
+ AAPT_ASSERT_TRUE(style->parent);
+ EXPECT_TRUE(style->parent.value().private_reference);
}
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>(&table_, "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 =
+ table_.FindResource(test::ParseNameOrDie("styleable/foo"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
- Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar");
- ASSERT_NE(attr, nullptr);
- EXPECT_TRUE(attr->isWeak());
+ Attribute* attr = test::GetValue<Attribute>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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].private_reference);
+ 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].private_reference);
+ 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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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>(&table_, "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 +591,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>(&table_, "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>(&table_, "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 +613,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>(&table_, "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 +629,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>(
+ &table_, "string/foo",
+ ConfigDescription::DefaultConfig(), "phone"));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "string/foo",
+ ConfigDescription::DefaultConfig(), "no-sdcard"));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfigAndProduct<String>(
+ &table_, "string/bar", ConfigDescription::DefaultConfig(), ""));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfigAndProduct<String>(
+ &table_, "string/baz", ConfigDescription::DefaultConfig(), ""));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "string/bit",
+ ConfigDescription::DefaultConfig(), "phablet"));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
+ &table_, "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 =
+ table_.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(),
- result.value().type->id.value(),
- result.value().entry->id.value());
- EXPECT_EQ(ResourceId(0x01010040), actualId);
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ ResourceId actual_id(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010040), actual_id);
- result = mTable.findResource(test::parseNameOrDie("attr/bar"));
- AAPT_ASSERT_TRUE(result);
+ result = table_.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);
+ AAPT_ASSERT_TRUE(result.value().package->id);
+ AAPT_ASSERT_TRUE(result.value().type->id);
+ AAPT_ASSERT_TRUE(result.value().entry->id);
+ actual_id = ResourceId(result.value().package->id.value(),
+ result.value().type->id.value(),
+ result.value().entry->id.value());
+ EXPECT_EQ(ResourceId(0x01010041), actual_id);
}
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 =
+ table_.FindResource(test::ParseNameOrDie("string/bar"));
+ AAPT_ASSERT_TRUE(result);
+ const ResourceEntry* entry = result.value().entry;
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.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>(&table_, "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..4e6a50a 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -14,551 +14,543 @@
* 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 <android-base/logging.h>
#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 less_than_type(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;
+static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs,
+ const StringPiece& rhs) {
+ 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;
+ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
+ const auto last = packages.end();
+ auto iter =
+ std::lower_bound(packages.begin(), last, name,
+ less_than_struct_with_name<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();
- }
+ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
+ 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;
-}
+ }
-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();
-}
-
-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();
- }
+ if (id && package->id && package->id.value() != id.value()) {
return nullptr;
+ }
+ return package;
}
-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();
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(
+ const StringPiece& name) {
+ const auto last = packages.end();
+ auto iter =
+ std::lower_bound(packages.begin(), last, name,
+ less_than_struct_with_name<ResourceTablePackage>);
+ if (iter != last && name == (*iter)->name) {
+ return iter->get();
+ }
+
+ std::unique_ptr<ResourceTablePackage> new_package =
+ util::make_unique<ResourceTablePackage>();
+ new_package->name = name.ToString();
+ return packages.emplace(iter, std::move(new_package))->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;
+ResourceTableType* ResourceTablePackage::FindType(ResourceType type) {
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, less_than_type);
+ if (iter != last && (*iter)->type == type) {
+ 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();
+ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) {
+ const auto last = types.end();
+ auto iter = std::lower_bound(types.begin(), last, type, less_than_type);
+ if (iter != last && (*iter)->type == type) {
+ return iter->get();
+ }
+ return types.emplace(iter, new ResourceTableType(type))->get();
}
-ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) {
- return findValue(config, StringPiece());
+ResourceEntry* ResourceTableType::FindEntry(const StringPiece& name) {
+ const auto last = entries.end();
+ auto iter = std::lower_bound(entries.begin(), last, name,
+ less_than_struct_with_name<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,
+ less_than_struct_with_name<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());
}
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,
+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());
- }
+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());
}
- 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;
+ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(
+ Value* existing, Value* incoming) {
+ Attribute* existing_attr = ValueCast<Attribute>(existing);
+ Attribute* incoming_attr = ValueCast<Attribute>(incoming);
+ if (!incoming_attr) {
+ 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 (!existing_attr) {
+ 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;
+ }
+
+ CHECK(incoming_attr != nullptr && existing_attr != nullptr);
+
+ //
+ // 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 (existing_attr->type_mask == incoming_attr->type_mask) {
+ // The two attributes are both DECLs, but they are plain attributes
+ // with the same formats.
+ // Keep the strongest one.
+ return existing_attr->IsWeak() ? CollisionResult::kTakeNew
+ : CollisionResult::kKeepOriginal;
+ }
+
+ if (existing_attr->IsWeak() &&
+ existing_attr->type_mask == android::ResTable_map::TYPE_ANY) {
+ // Any incoming attribute is better than this.
+ return CollisionResult::kTakeNew;
+ }
+
+ if (incoming_attr->IsWeak() &&
+ incoming_attr->type_mask == 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 = "._-";
static constexpr const char* kValidNameMangledChars = "._-$";
-bool ResourceTable::addResource(const ResourceNameRef& name,
+bool ResourceTable::AddResource(const ResourceNameRef& name,
const ConfigDescription& config,
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,
- const ResourceId& resId,
+bool ResourceTable::AddResource(const ResourceNameRef& name,
+ const ResourceId& res_id,
const ConfigDescription& config,
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, res_id, config, product, std::move(value),
+ kValidNameChars, ResolveValueCollision, diag);
}
-bool ResourceTable::addFileReference(const ResourceNameRef& name,
+bool ResourceTable::AddFileReference(const ResourceNameRef& name,
const ConfigDescription& config,
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* valid_chars, IDiagnostics* diag) {
+ std::unique_ptr<FileReference> fileRef =
+ util::make_unique<FileReference>(string_pool.MakeRef(path));
+ fileRef->SetSource(source);
+ fileRef->file = file;
+ return AddResourceImpl(name, ResourceId{}, config, StringPiece{},
+ std::move(fileRef), valid_chars, ResolveValueCollision,
+ diag);
}
-bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
const ConfigDescription& config,
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,
+bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
const ResourceId& id,
const ConfigDescription& config,
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& res_id,
+ const ConfigDescription& config, const StringPiece& product,
+ std::unique_ptr<Value> value, const char* valid_chars,
+ const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) {
+ CHECK(value != nullptr);
+ CHECK(diag != 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)
- << "'");
+ auto bad_char_iter =
+ util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
+ if (bad_char_iter != name.entry.end()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '"
+ << StringPiece(bad_char_iter, 1) << "'");
+ return false;
+ }
+
+ ResourceTablePackage* package = FindOrCreatePackage(name.package);
+ if (res_id.is_valid() && package->id &&
+ package->id.value() != res_id.package_id()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but package '" << package->name << "' already has ID "
+ << std::hex << (int)package->id.value() << std::dec);
+ return false;
+ }
+
+ ResourceTableType* type = package->FindOrCreateType(name.type);
+ if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but type '" << type->type << "' already has ID "
+ << std::hex << (int)type->id.value() << std::dec);
+ return false;
+ }
+
+ ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+ if (res_id.is_valid() && entry->id &&
+ entry->id.value() != res_id.entry_id()) {
+ diag->Error(DiagMessage(value->GetSource())
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " but resource already has ID "
+ << ResourceId(package->id.value(), type->id.value(),
+ entry->id.value()));
+ return false;
+ }
+
+ ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
+ if (!config_value->value) {
+ // Resource does not exist, add it now.
+ config_value->value = std::move(value);
+
+ } else {
+ switch (conflictResolver(config_value->value.get(), value.get())) {
+ case CollisionResult::kTakeNew:
+ // Take the incoming value.
+ config_value->value = std::move(value);
+ break;
+
+ case CollisionResult::kConflict:
+ diag->Error(DiagMessage(value->GetSource())
+ << "duplicate value for resource '" << name << "' "
+ << "with config '" << config << "'");
+ diag->Error(DiagMessage(config_value->value->GetSource())
+ << "resource previously defined here");
return false;
+
+ case CollisionResult::kKeepOriginal:
+ break;
}
+ }
- 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;
- }
-
- 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.
- 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;
-
- 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;
- }
- }
-
- if (resId.isValid()) {
- package->id = resId.packageId();
- type->id = resId.typeId();
- entry->id = resId.entryId();
- }
- return true;
+ if (res_id.is_valid()) {
+ package->id = res_id.package_id();
+ type->id = res_id.type_id();
+ entry->id = res_id.entry_id();
+ }
+ return true;
}
-bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId& resId,
+bool ResourceTable::SetSymbolState(const ResourceNameRef& name,
+ const ResourceId& res_id,
const Symbol& symbol, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
+ return SetSymbolStateImpl(name, res_id, symbol, kValidNameChars, diag);
}
-bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
- const ResourceId& resId,
- const Symbol& symbol, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
+bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId& res_id,
+ const Symbol& symbol,
+ IDiagnostics* diag) {
+ return SetSymbolStateImpl(name, res_id, 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& res_id,
+ const Symbol& symbol,
+ const char* valid_chars,
IDiagnostics* diag) {
- assert(diag && "diagnostics can't be nullptr");
+ CHECK(diag != 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 bad_char_iter =
+ util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
+ if (bad_char_iter != name.entry.end()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "resource '" << name << "' has invalid entry name '"
+ << name.entry << "'. Invalid character '"
+ << StringPiece(bad_char_iter, 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 (res_id.is_valid() && package->id &&
+ package->id.value() != res_id.package_id()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " 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 (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " 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 (res_id.is_valid() && entry->id &&
+ entry->id.value() != res_id.entry_id()) {
+ diag->Error(DiagMessage(symbol.source)
+ << "trying to add resource '" << name << "' with ID " << res_id
+ << " 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 (res_id.is_valid()) {
+ package->id = res_id.package_id();
+ type->id = res_id.type_id();
+ entry->id = res_id.entry_id();
+ }
- // 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->symbol_status.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->symbol_status.state != SymbolState::kUndefined) {
+ // We can't undefine a symbol (remove its visibility). Ignore.
return true;
+ }
+
+ if (symbol.state == SymbolState::kPrivate &&
+ entry->symbol_status.state == SymbolState::kPublic) {
+ // We can't downgrade public to private. Ignore.
+ return true;
+ }
+
+ entry->symbol_status = 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..a0c3217 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 symbol_status;
- /**
- * 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 symbol_status;
- /**
- * 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& res_id,
+ 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& res_id,
+ 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& res_id,
+ 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: `string_pool` 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 string_pool;
-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& res_id,
+ const ConfigDescription& config,
+ const StringPiece& product, std::unique_ptr<Value> value,
+ const char* valid_chars,
+ const CollisionResolverFunc& conflict_resolver,
+ IDiagnostics* diag);
+
+ bool AddFileReferenceImpl(const ResourceNameRef& name,
+ const ConfigDescription& config,
+ const Source& source, const StringPiece& path,
+ io::IFile* file, const char* valid_chars,
IDiagnostics* diag);
- DISALLOW_COPY_AND_ASSIGN(ResourceTable);
+ bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
+ const Symbol& symbol, const char* valid_chars,
+ 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..cb3699a0 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 language_config;
+ memcpy(language_config.language, "pl", sizeof(language_config.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"), language_config, "",
+ 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", language_config));
}
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..fce9b33 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -14,201 +14,211 @@
* limitations under the License.
*/
-#include "NameMangler.h"
#include "ResourceUtils.h"
+
+#include <sstream>
+
+#include "androidfw/ResourceTypes.h"
+
+#include "NameMangler.h"
#include "SdkConstants.h"
#include "flatten/ResourceTypeExtensions.h"
#include "util/Files.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <sstream>
-
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& name_in) {
+ ResourceName name_out;
+ if (!name_in.package) {
+ return {};
+ }
- nameOut.package = util::utf16ToUtf8(StringPiece16(nameIn.package, nameIn.packageLen));
+ name_out.package =
+ util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.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 (name_in.type) {
+ type = ParseResourceType(
+ util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen)));
+ } else if (name_in.type8) {
+ type = ParseResourceType(StringPiece(name_in.type8, name_in.typeLen));
+ } else {
+ return {};
+ }
- if (!type) {
- return {};
- }
+ if (!type) {
+ return {};
+ }
- nameOut.type = *type;
+ name_out.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 (name_in.name) {
+ name_out.entry =
+ util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen));
+ } else if (name_in.name8) {
+ name_out.entry = StringPiece(name_in.name8, name_in.nameLen).ToString();
+ } else {
+ return {};
+ }
+ return name_out;
}
-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 ExtractResourceName(const StringPiece& str, StringPiece* out_package,
+ StringPiece* out_type, StringPiece* out_entry) {
+ bool has_package_separator = false;
+ bool has_type_separator = false;
+ const char* start = str.data();
+ const char* end = start + str.size();
+ const char* current = start;
+ while (current != end) {
+ if (out_type->size() == 0 && *current == '/') {
+ has_type_separator = true;
+ out_type->assign(start, current - start);
+ start = current + 1;
+ } else if (out_package->size() == 0 && *current == ':') {
+ has_package_separator = true;
+ out_package->assign(start, current - start);
+ start = current + 1;
}
- outEntry->assign(start, end - start);
+ current++;
+ }
+ out_entry->assign(start, end - start);
- return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
+ return !(has_package_separator && out_package->empty()) &&
+ !(has_type_separator && out_type->empty());
}
-bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef, bool* outPrivate) {
- if (str.empty()) {
- return false;
+bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
+ bool* out_private) {
+ 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* parsed_type = ParseResourceType(type);
+ if (!parsed_type) {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (out_ref) {
+ out_ref->package = package;
+ out_ref->type = *parsed_type;
+ out_ref->entry = entry;
+ }
+
+ if (out_private) {
+ *out_private = priv;
+ }
+ return true;
+}
+
+bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
+ bool* out_create, bool* out_private) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ if (trimmed_str.empty()) {
+ return false;
+ }
+
+ bool create = false;
+ bool priv = false;
+ if (trimmed_str.data()[0] == '@') {
+ size_t offset = 1;
+ if (trimmed_str.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(
+ trimmed_str.substr(offset, trimmed_str.size() - offset), &name,
+ &priv)) {
+ return false;
}
+ if (create && priv) {
+ return false;
+ }
+
+ if (create && name.type != ResourceType::kId) {
+ return false;
+ }
+
+ if (out_ref) {
+ *out_ref = name;
+ }
+
+ if (out_create) {
+ *out_create = create;
+ }
+
+ if (out_private) {
+ *out_private = priv;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool IsReference(const StringPiece& str) {
+ return ParseReference(str, nullptr, nullptr, nullptr);
+}
+
+bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ if (trimmed_str.empty()) {
+ return false;
+ }
+
+ if (*trimmed_str.data() == '?') {
StringPiece package;
StringPiece type;
StringPiece entry;
- if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
- return false;
+ if (!ExtractResourceName(trimmed_str.substr(1, trimmed_str.size() - 1),
+ &package, &type, &entry)) {
+ return false;
}
- const ResourceType* parsedType = parseResourceType(type);
- if (!parsedType) {
- return false;
+ if (!type.empty() && type != "attr") {
+ return false;
}
if (entry.empty()) {
- return false;
+ return false;
}
- if (outRef) {
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
- }
-
- if (outPrivate) {
- *outPrivate = priv;
+ if (out_ref) {
+ out_ref->package = package;
+ out_ref->type = ResourceType::kAttr;
+ out_ref->entry = entry;
}
return true;
+ }
+ return false;
}
-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;
-}
-
-bool isReference(const StringPiece& str) {
- 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;
- }
- return false;
-}
-
-bool isAttributeReference(const StringPiece& str) {
- return parseAttributeReference(str, nullptr);
+bool IsAttributeReference(const StringPiece& str) {
+ return ParseAttributeReference(str, nullptr);
}
/*
@@ -219,463 +229,473 @@
* <[*]package>:[style/]<entry>
* [[*]package:style/]<entry>
*/
-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 {};
- }
- }
-
- 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;
-}
-
-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;
-
- 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++;
- }
-
- 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;
- }
-
- if (parseAttributeReference(str, &ref)) {
- if (outCreate) {
- *outCreate = false;
- }
- return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
- }
+Maybe<Reference> ParseStyleParentReference(const StringPiece& str,
+ std::string* out_error) {
+ if (str.empty()) {
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 name = str;
-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);
- }
+ bool has_leading_identifiers = false;
+ bool private_ref = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == '@' || name.data()[0] == '?') {
+ has_leading_identifiers = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ if (name.data()[0] == '*') {
+ private_ref = true;
+ name = name.substr(1, name.size() - 1);
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece type_str;
+ ExtractResourceName(name, &ref.package, &type_str, &ref.entry);
+ if (!type_str.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsed_type = ParseResourceType(type_str);
+ if (!parsed_type || *parsed_type != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << type_str << "' for parent of style";
+ *out_error = err.str();
+ return {};
}
+ }
+
+ if (!has_leading_identifiers && ref.package.empty() && !type_str.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *out_error = err.str();
return {};
+ }
+
+ Reference result(ref);
+ result.private_reference = private_ref;
+ return result;
}
-std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
+Maybe<Reference> ParseXmlAttributeName(const StringPiece& str) {
+ StringPiece trimmed_str = util::TrimWhitespace(str);
+ const char* start = trimmed_str.data();
+ const char* const end = start + trimmed_str.size();
+ const char* p = start;
+
+ Reference ref;
+ if (p != end && *p == '*') {
+ ref.private_reference = 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++;
+ }
+
+ ref.name =
+ ResourceName(package.ToString(), ResourceType::kAttr,
+ name.empty() ? trimmed_str.ToString() : name.ToString());
+ return Maybe<Reference>(std::move(ref));
+}
+
+std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
+ bool* out_create) {
+ ResourceNameRef ref;
+ bool private_ref = false;
+ if (ParseReference(str, &ref, out_create, &private_ref)) {
+ std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+ value->private_reference = private_ref;
+ return value;
+ }
+
+ if (ParseAttributeReference(str, &ref)) {
+ if (out_create) {
+ *out_create = false;
+ }
+ return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ android::Res_value value = {};
+ if (trimmed_str == "@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 (trimmed_str == "@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* enum_attr,
const StringPiece& str) {
- 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);
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ for (const Attribute::Symbol& symbol : enum_attr->symbols) {
+ // Enum symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value();
+ if (trimmed_str == enum_symbol_resource_name.entry) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.data = symbol.value;
+ return util::make_unique<BinaryPrimitive>(value);
}
+ }
+ return {};
+}
- for (StringPiece part : util::tokenize(str, '|')) {
- StringPiece trimmedPart = util::trimWhitespace(part);
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
+ const StringPiece& str) {
+ android::Res_value flags = {};
+ flags.dataType = android::Res_value::TYPE_INT_HEX;
+ flags.data = 0u;
- 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 trimmed_part = util::TrimWhitespace(part);
+
+ bool flag_set = false;
+ for (const Attribute::Symbol& symbol : flag_attr->symbols) {
+ // Flag symbols are stored as @package:id/symbol resources,
+ // so we need to match against the 'entry' part of the identifier.
+ const ResourceName& flag_symbol_resource_name =
+ symbol.symbol.name.value();
+ if (trimmed_part == flag_symbol_resource_name.entry) {
+ flags.data |= symbol.value;
+ flag_set = true;
+ break;
+ }
+ }
+
+ if (!flag_set) {
+ 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;
+static uint32_t ParseHex(char c, bool* out_error) {
+ 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 {
+ *out_error = true;
+ return 0xffffffffu;
+ }
+}
+
+std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
+ StringPiece color_str(util::TrimWhitespace(str));
+ const char* start = color_str.data();
+ const size_t len = color_str.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);
+}
+
+Maybe<bool> ParseBool(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+ if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
+ return Maybe<bool>(true);
+ } else if (trimmed_str == "false" || trimmed_str == "FALSE" ||
+ trimmed_str == "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 {};
+}
+
+Maybe<ResourceId> ParseResourceId(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+
+ std::u16string str16 = util::Utf8ToUtf16(trimmed_str);
+ 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.is_valid()) {
+ return id;
+ }
+ }
+ }
+ return {};
+}
+
+Maybe<int> ParseSdkVersion(const StringPiece& str) {
+ StringPiece trimmed_str(util::TrimWhitespace(str));
+
+ std::u16string str16 = util::Utf8ToUtf16(trimmed_str);
+ 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 == trimmed_str) {
+ return entry.second;
+ }
+ return {};
+}
+
+std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) {
+ if (Maybe<bool> maybe_result = ParseBool(str)) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+
+ if (maybe_result.value()) {
+ value.data = 0xffffffffu;
} 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 {};
- }
-
- 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 {};
-}
-
-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 {};
-}
-
-Maybe<ResourceId> parseResourceId(const StringPiece& 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;
- }
- }
- }
- return {};
-}
-
-Maybe<int> parseSdkVersion(const StringPiece& 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);
- }
-
- // 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 (maybeResult.value()) {
- value.data = 0xffffffffu;
- } else {
- value.data = 0;
- }
- 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 {};
+ value.data = 0;
}
return util::make_unique<BinaryPrimitive>(value);
+ }
+ return {};
}
-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::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);
}
-uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
- switch (type) {
+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);
+}
+
+uint32_t AndroidTypeToAttributeTypeMask(uint16_t 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);
- }
+std::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& value, uint32_t type_mask,
+ const std::function<void(const ResourceName&)>& on_create_reference) {
+ std::unique_ptr<BinaryPrimitive> null_or_empty = TryParseNullOrEmpty(value);
+ if (null_or_empty) {
+ return std::move(null_or_empty);
+ }
- 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 && on_create_reference) {
+ on_create_reference(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 (type_mask & 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 (type_mask & 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 (type_mask & 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 float_mask = android::ResTable_map::TYPE_FLOAT |
+ android::ResTable_map::TYPE_DIMENSION |
+ android::ResTable_map::TYPE_FRACTION;
+ if (type_mask & float_mask) {
+ // Try parsing this as a float.
+ std::unique_ptr<BinaryPrimitive> floating_point = TryParseFloat(value);
+ if (floating_point) {
+ if (type_mask &
+ AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
+ return std::move(floating_point);
+ }
}
- return {};
+ }
+ return {};
}
/**
* We successively try to parse the string as a resource type that the Attribute
* 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;
- }
+std::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& str, const Attribute* attr,
+ const std::function<void(const ResourceName&)>& on_create_reference) {
+ const uint32_t type_mask = attr->type_mask;
+ std::unique_ptr<Item> value =
+ TryParseItemForAttribute(str, type_mask, on_create_reference);
+ 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 (type_mask & android::ResTable_map::TYPE_ENUM) {
+ // Try parsing this as an enum.
+ std::unique_ptr<BinaryPrimitive> enum_value = TryParseEnumSymbol(attr, str);
+ if (enum_value) {
+ return std::move(enum_value);
}
+ }
- 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 (type_mask & android::ResTable_map::TYPE_FLAGS) {
+ // Try parsing this as a flag.
+ std::unique_ptr<BinaryPrimitive> flag_value = TryParseFlagSymbol(attr, str);
+ if (flag_value) {
+ return std::move(flag_value);
}
- 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& res_file,
+ const NameMangler* mangler) {
+ std::stringstream out;
+ out << "res/" << res_file.name.type;
+ if (res_file.config != ConfigDescription{}) {
+ out << "-" << res_file.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(res_file.name.package)) {
+ out << NameMangler::MangleEntry(res_file.name.package, res_file.name.entry);
+ } else {
+ out << res_file.name.entry;
+ }
+ out << file::GetExtension(res_file.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..9766f6a 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -17,14 +17,14 @@
#ifndef AAPT_RESOURCEUTILS_H
#define AAPT_RESOURCEUTILS_H
+#include <functional>
+#include <memory>
+
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceValues.h"
#include "util/StringPiece.h"
-#include <functional>
-#include <memory>
-
namespace aapt {
namespace ResourceUtils {
@@ -37,164 +37,183 @@
* individual extracted piece to verify that the pieces are valid.
* Returns false if there was no package but a ':' was present.
*/
-bool extractResourceName(const StringPiece& str, StringPiece* outPackage,
- StringPiece* outType, StringPiece* outEntry);
+bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
+ StringPiece* out_type, StringPiece* out_entry);
/**
- * 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.
+ * Returns true if the string was parsed as a resource name
+ * ([*][package:]type/name), with
+ * `out_resource` set to the parsed resource name and `out_private` set to true
+ * if a '*' prefix was present.
*/
-bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource,
- bool* outPrivate = nullptr);
+bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_resource,
+ bool* out_private = nullptr);
/*
- * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
- * `outReference` set to the parsed reference.
+ * Returns true if the string was parsed as a reference
+ * (@[+][package:]type/name), with
+ * `out_reference` set to the parsed reference.
*
- * If '+' was present in the reference, `outCreate` is set to true.
- * If '*' was present in the reference, `outPrivate` is set to true.
+ * If '+' was present in the reference, `out_create` is set to true.
+ * If '*' was present in the reference, `out_private` is set to true.
*/
-bool parseReference(const StringPiece& str, ResourceNameRef* outReference,
- bool* outCreate = nullptr, bool* outPrivate = nullptr);
+bool ParseReference(const StringPiece& str, ResourceNameRef* out_reference,
+ bool* out_create = nullptr, bool* out_private = 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);
+bool IsReference(const StringPiece& str);
/*
- * Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
- * with `outReference` set to the parsed reference.
+ * Returns true if the string was parsed as an attribute reference
+ * (?[package:][type/]name),
+ * with `out_reference` set to the parsed reference.
*/
-bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outReference);
+bool ParseAttributeReference(const StringPiece& str,
+ ResourceNameRef* out_reference);
/**
- * 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);
+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);
+Maybe<bool> ParseBool(const StringPiece& str);
/**
* Returns a uint32_t if the string is an integer.
*/
-Maybe<uint32_t> parseInt(const StringPiece& str);
+Maybe<uint32_t> ParseInt(const StringPiece& str);
/**
* Returns an ID if it the string represented a valid ID.
*/
-Maybe<ResourceId> parseResourceId(const StringPiece& str);
+Maybe<ResourceId> ParseResourceId(const StringPiece& str);
/**
* Parses an SDK version, which can be an integer, or a letter from A-Z.
*/
-Maybe<int> parseSdkVersion(const StringPiece& str);
+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* out_error);
/*
- * 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
*/
-Maybe<Reference> parseXmlAttributeName(const StringPiece& str);
+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* out_create = nullptr);
/*
- * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing @null or @empty if the string
+ * was parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a color if the string was parsed
* as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing a boolean if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing a boolean if the string was
+ * parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing an integer if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing an integer if the string was
+ * parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a floating point number
* (float, dimension, etc) if the string was parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing an enum symbol if the string was
+ * parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
const StringPiece& str);
/*
- * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
- * as one.
+ * Returns a BinaryPrimitve object representing a flag symbol if the string was
+ * parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
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
+ * The callback function on_create_reference 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 = {});
+std::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& value, const Attribute* attr,
+ const std::function<void(const ResourceName&)>& on_create_reference = {});
-std::unique_ptr<Item> tryParseItemForAttribute(
- const StringPiece& value, uint32_t typeMask,
- const std::function<void(const ResourceName&)>& onCreateReference = {});
+std::unique_ptr<Item> TryParseItemForAttribute(
+ const StringPiece& value, uint32_t type_mask,
+ const std::function<void(const ResourceName&)>& on_create_reference = {});
-uint32_t androidTypeToAttributeTypeMask(uint16_t type);
+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& res_file,
+ const NameMangler* mangler = nullptr);
-} // 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..f9c500b 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -14,179 +14,191 @@
* 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 actual_priv = false;
+ EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual,
+ &actual_priv));
+ EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
+ EXPECT_FALSE(actual_priv);
- 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, &actual_priv));
+ EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
+ EXPECT_FALSE(actual_priv);
- 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,
+ &actual_priv));
+ EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
+ EXPECT_TRUE(actual_priv);
- EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv));
+ EXPECT_FALSE(
+ ResourceUtils::ParseResourceName(StringPiece(), &actual, &actual_priv));
}
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 private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create,
+ &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(private_ref);
}
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 private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual,
+ &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(private_ref);
}
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 private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t",
+ &actual, &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_FALSE(private_ref);
}
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 private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual,
+ &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(create);
+ EXPECT_FALSE(private_ref);
}
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 private_ref = false;
+ EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual,
+ &create, &private_ref));
+ EXPECT_EQ(expected, actual);
+ EXPECT_FALSE(create);
+ EXPECT_TRUE(private_ref);
}
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 private_ref = false;
+ ResourceNameRef actual;
+ EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual,
+ &create, &private_ref));
}
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 err_str;
+ Maybe<Reference> ref =
+ ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ 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", &err_str);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ EXPECT_TRUE(ref.value().private_reference);
}
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..7956ad8 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -14,757 +14,760 @@
* limitations under the License.
*/
-#include "Resource.h"
-#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "ValueVisitor.h"
-#include "util/Util.h"
#include <algorithm>
-#include <androidfw/ResourceTypes.h>
#include <limits>
#include <set>
+#include "androidfw/ResourceTypes.h"
+
+#include "Resource.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "util/Util.h"
+
namespace aapt {
template <typename Derived>
-void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+void BaseValue<Derived>::Accept(RawValueVisitor* visitor) {
+ visitor->Visit(static_cast<Derived*>(this));
}
template <typename Derived>
-void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+void BaseItem<Derived>::Accept(RawValueVisitor* visitor) {
+ 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;
-}
-
-RawString* RawString::clone(StringPool* newPool) const {
- 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;
-}
-
-void RawString::print(std::ostream* out) const {
- *out << "(raw string) " << *value;
-}
-
-Reference::Reference() : referenceType(Type::kResource) {
-}
-
-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 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;
-}
-
-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;
-}
-
-Reference* Reference::clone(StringPool* /*newPool*/) const {
- return new Reference(*this);
-}
-
-void Reference::print(std::ostream* out) const {
- *out << "(reference) ";
- if (referenceType == Reference::Type::kResource) {
- *out << "@";
- if (privateReference) {
- *out << "*";
- }
- } else {
- *out << "?";
- }
-
- if (name) {
- *out << name.value();
- }
-
- if (id && !Res_INTERNALID(id.value().id)) {
- *out << " " << id.value();
- }
-}
-
-bool Id::equals(const Value* value) const {
- 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;
-}
-
-Id* Id::clone(StringPool* /*newPool*/) const {
- return new Id(*this);
-}
-
-void Id::print(std::ostream* out) const {
- *out << "(id)";
-}
-
-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;
-}
-
-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;
- }
-
- 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;
-}
-
-void String::print(std::ostream* out) const {
- *out << "(string) \"" << *value << "\"";
-}
-
-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;
- });
- }
+bool RawString::Equals(const Value* value) const {
+ const RawString* other = ValueCast<RawString>(value);
+ if (!other) {
return false;
+ }
+ return *this->value == *other->value;
}
-bool StyledString::flatten(android::Res_value* outValue) const {
- if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
- return false;
+RawString* RawString::Clone(StringPool* new_pool) const {
+ RawString* rs = new RawString(new_pool->MakeRef(*value));
+ rs->comment_ = comment_;
+ rs->source_ = source_;
+ return rs;
+}
+
+bool RawString::Flatten(android::Res_value* out_value) const {
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
+ return true;
+}
+
+void RawString::Print(std::ostream* out) const {
+ *out << "(raw string) " << *value;
+}
+
+Reference::Reference() : reference_type(Type::kResource) {}
+
+Reference::Reference(const ResourceNameRef& n, Type t)
+ : name(n.ToResourceName()), reference_type(t) {}
+
+Reference::Reference(const ResourceId& i, Type type)
+ : id(i), reference_type(type) {}
+
+Reference::Reference(const ResourceNameRef& n, const ResourceId& i)
+ : name(n.ToResourceName()), id(i), reference_type(Type::kResource) {}
+
+bool Reference::Equals(const Value* value) const {
+ const Reference* other = ValueCast<Reference>(value);
+ if (!other) {
+ return false;
+ }
+ return reference_type == other->reference_type &&
+ private_reference == other->private_reference && id == other->id &&
+ name == other->name;
+}
+
+bool Reference::Flatten(android::Res_value* out_value) const {
+ out_value->dataType = (reference_type == Reference::Type::kResource)
+ ? android::Res_value::TYPE_REFERENCE
+ : android::Res_value::TYPE_ATTRIBUTE;
+ out_value->data = util::HostToDevice32(id ? id.value().id : 0);
+ return true;
+}
+
+Reference* Reference::Clone(StringPool* /*new_pool*/) const {
+ return new Reference(*this);
+}
+
+void Reference::Print(std::ostream* out) const {
+ *out << "(reference) ";
+ if (reference_type == Reference::Type::kResource) {
+ *out << "@";
+ if (private_reference) {
+ *out << "*";
}
+ } else {
+ *out << "?";
+ }
- outValue->dataType = android::Res_value::TYPE_STRING;
- outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
- return true;
+ if (name) {
+ *out << name.value();
+ }
+
+ if (id && !Res_INTERNALID(id.value().id)) {
+ *out << " " << id.value();
+ }
}
-StyledString* StyledString::clone(StringPool* newPool) const {
- StyledString* str = new StyledString(newPool->makeRef(value));
- str->mComment = mComment;
- str->mSource = mSource;
- return str;
+bool Id::Equals(const Value* value) const {
+ return ValueCast<Id>(value) != nullptr;
}
-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;
- }
+bool Id::Flatten(android::Res_value* out) const {
+ out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
+ out->data = util::HostToDevice32(0);
+ return true;
}
-FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
+Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); }
+
+void Id::Print(std::ostream* out) const { *out << "(id)"; }
+
+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;
}
-bool FileReference::equals(const Value* value) const {
- const FileReference* other = valueCast<FileReference>(value);
- if (!other) {
- return false;
- }
- return *path == *other->path;
+bool String::Flatten(android::Res_value* out_value) const {
+ // Verify that our StringPool index is within encode-able limits.
+ if (value.index() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
+ return true;
}
-bool FileReference::flatten(android::Res_value* outValue) const {
- 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;
+String* String::Clone(StringPool* new_pool) const {
+ String* str = new String(new_pool->MakeRef(*value));
+ str->comment_ = comment_;
+ str->source_ = source_;
+ return str;
}
-FileReference* FileReference::clone(StringPool* newPool) const {
- FileReference* fr = new FileReference(newPool->makeRef(*path));
- fr->file = file;
- fr->mComment = mComment;
- fr->mSource = mSource;
- return fr;
+void String::Print(std::ostream* out) const {
+ *out << "(string) \"" << *value << "\"";
}
-void FileReference::print(std::ostream* out) const {
- *out << "(file) " << *path;
+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>& spans_a = this->value->spans;
+ const std::vector<StringPool::Span>& spans_b = other->value->spans;
+ return std::equal(
+ spans_a.begin(), spans_a.end(), spans_b.begin(),
+ [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
+ return *a.name == *b.name && a.first_char == b.first_char &&
+ a.last_char == b.last_char;
+ });
+ }
+ return false;
}
-BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
+bool StyledString::Flatten(android::Res_value* out_value) const {
+ if (value.index() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
+ return true;
}
+StyledString* StyledString::Clone(StringPool* new_pool) const {
+ StyledString* str = new StyledString(new_pool->MakeRef(value));
+ str->comment_ = comment_;
+ str->source_ = source_;
+ 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.first_char << ","
+ << span.last_char;
+ }
+}
+
+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;
+}
+
+bool FileReference::Flatten(android::Res_value* out_value) const {
+ if (path.index() > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+
+ out_value->dataType = android::Res_value::TYPE_STRING;
+ out_value->data = util::HostToDevice32(static_cast<uint32_t>(path.index()));
+ return true;
+}
+
+FileReference* FileReference::Clone(StringPool* new_pool) const {
+ FileReference* fr = new FileReference(new_pool->MakeRef(*path));
+ fr->file = file;
+ fr->comment_ = comment_;
+ fr->source_ = source_;
+ return fr;
+}
+
+void FileReference::Print(std::ostream* out) const {
+ *out << "(file) " << *path;
+}
+
+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;
+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;
}
-bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
- outValue->dataType = value.dataType;
- outValue->data = util::hostToDevice32(value.data);
- return true;
+bool BinaryPrimitive::Flatten(android::Res_value* out_value) const {
+ out_value->dataType = value.dataType;
+ out_value->data = util::HostToDevice32(value.data);
+ return true;
}
-BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
- return new BinaryPrimitive(*this);
+BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const {
+ 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;
- }
+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;
+ }
}
-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)
+ : type_mask(t),
+ min_int(std::numeric_limits<int32_t>::min()),
+ max_int(std::numeric_limits<int32_t>::max()) {
+ weak_ = 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;
- }
+bool Attribute::Equals(const Value* value) const {
+ 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 (type_mask != other->type_mask || min_int != other->min_int ||
+ max_int != other->max_int) {
+ 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*> sorted_a;
+ std::transform(symbols.begin(), symbols.end(), std::back_inserter(sorted_a),
+ addPointer<const Symbol>);
+ std::sort(sorted_a.begin(), sorted_a.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*> sorted_b;
+ std::transform(other->symbols.begin(), other->symbols.end(),
+ std::back_inserter(sorted_b), addPointer<const Symbol>);
+ std::sort(sorted_b.begin(), sorted_b.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(sorted_a.begin(), sorted_a.end(), sorted_b.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);
+Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
+ return new Attribute(*this);
}
-void Attribute::printMask(std::ostream* out) const {
- if (typeMask == android::ResTable_map::TYPE_ANY) {
- *out << "any";
- return;
- }
+void Attribute::PrintMask(std::ostream* out) const {
+ if (type_mask == 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";
- }
-
- 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_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_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_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_FLAGS) != 0) {
- if (!set) {
- set = true;
- } else {
- *out << "|";
- }
- *out << "flags";
- }
-}
-
-void Attribute::print(std::ostream* out) const {
- *out << "(attr) ";
- printMask(out);
-
- if (!symbols.empty()) {
- *out << " [" << util::joiner(symbols, ", ") << "]";
- }
-
- if (minInt != std::numeric_limits<int32_t>::min()) {
- *out << " min=" << minInt;
- }
-
- if (maxInt != std::numeric_limits<int32_t>::max()) {
- *out << " max=" << maxInt;
- }
-
- if (isWeak()) {
- *out << " [weak]";
- }
-}
-
-static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
- const Item* value) {
- *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_DIMENSION) {
- *msg << " dimension";
- }
-
- 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_FLOAT) {
- *msg << " float";
- }
-
- 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_REFERENCE) {
- *msg << " reference";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
- }
-
- *msg << " but got " << *value;
-}
-
-bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
- 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;
- }
- }
- 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;
- }
-
- 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*> 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());
- });
-}
-
-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;
-}
-
-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 << " ["
- << 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();
+ bool set = false;
+ if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) {
+ if (!set) {
+ set = true;
} else {
- out << "???";
+ *out << "|";
}
- out << " = ";
- value.value->print(&out);
- return out;
+ *out << "reference";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "string";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "integer";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "boolean";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "color";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "float";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "dimension";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "fraction";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "enum";
+ }
+
+ if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) {
+ if (!set) {
+ set = true;
+ } else {
+ *out << "|";
+ }
+ *out << "flags";
+ }
}
-bool Array::equals(const Value* value) const {
- const Array* other = valueCast<Array>(value);
- if (!other) {
- return false;
- }
+void Attribute::Print(std::ostream* out) const {
+ *out << "(attr) ";
+ PrintMask(out);
- if (items.size() != other->items.size()) {
- return false;
- }
+ if (!symbols.empty()) {
+ *out << " [" << util::Joiner(symbols, ", ") << "]";
+ }
- 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());
- });
+ if (min_int != std::numeric_limits<int32_t>::min()) {
+ *out << " min=" << min_int;
+ }
+
+ if (max_int != std::numeric_limits<int32_t>::max()) {
+ *out << " max=" << max_int;
+ }
+
+ if (IsWeak()) {
+ *out << " [weak]";
+ }
}
-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;
+static void BuildAttributeMismatchMessage(DiagMessage* msg,
+ const Attribute* attr,
+ const Item* value) {
+ *msg << "expected";
+ if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
+
+ if (attr->type_mask & android::ResTable_map::TYPE_STRING) {
+ *msg << " string";
+ }
+
+ *msg << " but got " << *value;
}
-void Array::print(std::ostream* out) const {
- *out << "(array) ["
- << util::joiner(items, ", ")
- << "]";
+bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const {
+ android::Res_value val = {};
+ item->Flatten(&val);
+
+ // Always allow references.
+ const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) {
+ if (out_msg) {
+ BuildAttributeMismatchMessage(out_msg, this, item);
+ }
+ return false;
+
+ } else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) {
+ if (out_msg) {
+ *out_msg << *item << " is less than minimum integer " << min_int;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) {
+ if (out_msg) {
+ *out_msg << *item << " is greater than maximum integer " << max_int;
+ }
+ return false;
+ }
+ }
+ return true;
}
-bool Plural::equals(const Value* value) const {
- const Plural* other = valueCast<Plural>(value);
- if (!other) {
- return false;
- }
+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;
+ }
- if (values.size() != other->values.size()) {
- return false;
- }
+ if (entries.size() != other->entries.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());
- });
+ std::vector<const Entry*> sorted_a;
+ std::transform(entries.begin(), entries.end(), std::back_inserter(sorted_a),
+ addPointer<const Entry>);
+ std::sort(sorted_a.begin(), sorted_a.end(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.name < b->key.name;
+ });
+
+ std::vector<const Entry*> sorted_b;
+ std::transform(other->entries.begin(), other->entries.end(),
+ std::back_inserter(sorted_b), addPointer<const Entry>);
+ std::sort(sorted_b.begin(), sorted_b.end(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.name < b->key.name;
+ });
+
+ return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
+ [](const Entry* a, const Entry* b) -> bool {
+ return a->key.Equals(&b->key) &&
+ a->value->Equals(b->value.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));
- }
- }
- return p;
+Style* Style::Clone(StringPool* new_pool) const {
+ Style* style = new Style();
+ style->parent = parent;
+ style->parent_inferred = parent_inferred;
+ style->comment_ = comment_;
+ style->source_ = source_;
+ for (auto& entry : entries) {
+ style->entries.push_back(
+ Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))});
+ }
+ return style;
}
-void Plural::print(std::ostream* out) const {
- *out << "(plural)";
- if (values[Zero]) {
- *out << " zero=" << *values[Zero];
+void Style::Print(std::ostream* out) const {
+ *out << "(style) ";
+ if (parent && parent.value().name) {
+ if (parent.value().private_reference) {
+ *out << "*";
}
-
- if (values[One]) {
- *out << " one=" << *values[One];
- }
-
- if (values[Two]) {
- *out << " two=" << *values[Two];
- }
-
- if (values[Few]) {
- *out << " few=" << *values[Few];
- }
-
- if (values[Many]) {
- *out << " many=" << *values[Many];
- }
+ *out << parent.value().name.value();
+ }
+ *out << " [" << util::Joiner(entries, ", ") << "]";
}
-static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
- return out << *item;
+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 Styleable::equals(const Value* value) const {
- const Styleable* other = valueCast<Styleable>(value);
- if (!other) {
- return false;
+bool Array::Equals(const Value* value) const {
+ const Array* other = ValueCast<Array>(value);
+ if (!other) {
+ 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());
+ });
+}
+
+Array* Array::Clone(StringPool* new_pool) const {
+ Array* array = new Array();
+ array->comment_ = comment_;
+ array->source_ = source_;
+ for (auto& item : items) {
+ array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool)));
+ }
+ return array;
+}
+
+void Array::Print(std::ostream* out) const {
+ *out << "(array) [" << util::Joiner(items, ", ") << "]";
+}
+
+bool Plural::Equals(const Value* value) const {
+ const Plural* other = ValueCast<Plural>(value);
+ if (!other) {
+ 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());
+ });
+}
+
+Plural* Plural::Clone(StringPool* new_pool) const {
+ Plural* p = new Plural();
+ p->comment_ = comment_;
+ p->source_ = source_;
+ 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(new_pool));
}
-
- 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 p;
}
-Styleable* Styleable::clone(StringPool* /*newPool*/) const {
- return new Styleable(*this);
+void Plural::Print(std::ostream* out) const {
+ *out << "(plural)";
+ if (values[Zero]) {
+ *out << " zero=" << *values[Zero];
+ }
+
+ if (values[One]) {
+ *out << " one=" << *values[One];
+ }
+
+ if (values[Two]) {
+ *out << " two=" << *values[Two];
+ }
+
+ if (values[Few]) {
+ *out << " few=" << *values[Few];
+ }
+
+ if (values[Many]) {
+ *out << " many=" << *values[Many];
+ }
}
-void Styleable::print(std::ostream* out) const {
- *out << "(styleable) " << " ["
- << util::joiner(entries, ", ")
- << "]";
+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;
+ }
+
+ 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);
+ });
+}
+
+Styleable* Styleable::Clone(StringPool* /*new_pool*/) const {
+ return new Styleable(*this);
+}
+
+void Styleable::Print(std::ostream* out) const {
+ *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.value_or_default({}).compare(b.name.value_or_default({}));
+ 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());
+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());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 5e5d1f3..ea73615 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,17 +17,18 @@
#ifndef AAPT_RESOURCE_VALUES_H
#define AAPT_RESOURCE_VALUES_H
+#include <array>
+#include <ostream>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+
#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
#include "io/File.h"
#include "util/Maybe.h"
-#include <array>
-#include <androidfw/ResourceTypes.h>
-#include <ostream>
-#include <vector>
-
namespace aapt {
struct RawValueVisitor;
@@ -40,84 +41,65 @@
* 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 weak_; }
- void setWeak(bool val) {
- mWeak = val;
- }
+ void SetWeak(bool val) { weak_ = 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) { translateable_ = val; }
- // Default true.
- bool isTranslateable() const {
- return mTranslateable;
- }
+ // Default true.
+ bool IsTranslateable() const { return translateable_; }
- /**
- * 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 source_; }
- void setSource(const Source& source) {
- mSource = source;
- }
+ void SetSource(const Source& source) { source_ = source; }
- void setSource(Source&& source) {
- mSource = std::move(source);
- }
+ void SetSource(Source&& source) { source_ = 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 comment_; }
- void setComment(const StringPiece& str) {
- mComment = str.toString();
- }
+ void SetComment(const StringPiece& str) { comment_ = str.ToString(); }
- void setComment(std::string&& str) {
- mComment = std::move(str);
- }
+ void SetComment(std::string&& str) { comment_ = 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. new_pool is the new StringPool that
+ * any resources with strings should use when copying their string.
+ */
+ virtual Value* Clone(StringPool* new_pool) 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 source_;
+ std::string comment_;
+ bool weak_ = false;
+ bool translateable_ = true;
};
/**
@@ -125,23 +107,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* new_pool) 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* out_value) const = 0;
};
/**
@@ -149,35 +132,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 reference_type;
+ bool private_reference = 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);
+ 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* out_value) const override;
+ Reference* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
bool operator<(const Reference&, const Reference&);
@@ -187,11 +172,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() { weak_ = true; }
+ bool Equals(const Value* value) const override;
+ bool Flatten(android::Res_value* out) const override;
+ Id* Clone(StringPool* new_pool) const override;
+ void Print(std::ostream* out) const override;
};
/**
@@ -200,164 +185,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* out_value) const override;
+ RawString* Clone(StringPool* new_pool) 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* out_value) const override;
+ String* Clone(StringPool* new_pool) 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* out_value) const override;
+ StyledString* Clone(StringPool* new_pool) 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* out_value) const override;
+ FileReference* Clone(StringPool* new_pool) 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* out_value) const override;
+ BinaryPrimitive* Clone(StringPool* new_pool) 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 type_mask;
+ int32_t min_int;
+ int32_t max_int;
+ 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* new_pool) const override;
+ void PrintMask(std::ostream* out) const;
+ void Print(std::ostream* out) const override;
+ bool Matches(const Item* item, DiagMessage* out_msg) 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 parent_inferred = 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* new_pool) 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* new_pool) 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* new_pool) 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..720ab91 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -15,101 +15,102 @@
*/
#include "Resource.h"
+
#include "test/Test.h"
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..c7f920a 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 less_entry_id(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;
+size_t FindAttributeSdkLevel(const ResourceId& id) {
+ if (id.package_id() != 0x01 && id.type_id() != 0x01) {
+ return 0;
+ }
+ auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(),
+ id.entry_id(), less_entry_id);
+ 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;
- }
+size_t FindAttributeSdkLevel(const ResourceName& name) {
+ 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);
+std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion() {
+ 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..9b38ecb 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -17,40 +17,40 @@
#ifndef AAPT_SDK_CONSTANTS_H
#define AAPT_SDK_CONSTANTS_H
-#include "Resource.h"
-
#include <utility>
+#include "Resource.h"
+
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();
+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..716d922 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -16,23 +16,23 @@
#include "SdkConstants.h"
-#include <gtest/gtest.h>
+#include "gtest/gtest.h"
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..459a8e6 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -17,12 +17,12 @@
#ifndef AAPT_SOURCE_H
#define AAPT_SOURCE_H
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-
#include <ostream>
#include <string>
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
namespace aapt {
/**
@@ -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..3032829 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -15,399 +15,405 @@
*/
#include "StringPool.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
#include "util/BigBuffer.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-#include <algorithm>
-#include <androidfw/ResourceTypes.h>
-#include <memory>
-#include <string>
-
namespace aapt {
-StringPool::Ref::Ref() : mEntry(nullptr) {
+StringPool::Ref::Ref() : entry_(nullptr) {}
+
+StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
-StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
-}
-
-StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
- if (mEntry != nullptr) {
- mEntry->ref++;
- }
+StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
StringPool::Ref::~Ref() {
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
}
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
- if (rhs.mEntry != nullptr) {
- rhs.mEntry->ref++;
- }
+ if (rhs.entry_ != nullptr) {
+ rhs.entry_->ref_++;
+ }
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
- mEntry = rhs.mEntry;
- return *this;
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+ entry_ = rhs.entry_;
+ return *this;
}
const std::string* StringPool::Ref::operator->() const {
- return &mEntry->value;
+ return &entry_->value;
}
-const std::string& StringPool::Ref::operator*() const {
- return mEntry->value;
+const std::string& StringPool::Ref::operator*() const { return entry_->value; }
+
+size_t StringPool::Ref::index() const { return entry_->index; }
+
+const StringPool::Context& StringPool::Ref::GetContext() const {
+ return entry_->context;
}
-size_t StringPool::Ref::getIndex() const {
- return mEntry->index;
+StringPool::StyleRef::StyleRef() : entry_(nullptr) {}
+
+StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs)
+ : entry_(rhs.entry_) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
-const StringPool::Context& StringPool::Ref::getContext() const {
- return mEntry->context;
-}
-
-StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
-}
-
-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++;
- }
+StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
}
StringPool::StyleRef::~StyleRef() {
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
+ if (entry_ != nullptr) {
+ entry_->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.entry_ != nullptr) {
+ rhs.entry_->ref_++;
+ }
- if (mEntry != nullptr) {
- mEntry->ref--;
- }
- mEntry = rhs.mEntry;
- return *this;
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+ entry_ = rhs.entry_;
+ return *this;
}
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
- return mEntry;
+ return entry_;
}
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
- return *mEntry;
+ return *entry_;
}
-size_t StringPool::StyleRef::getIndex() const {
- return mEntry->str.getIndex();
+size_t StringPool::StyleRef::index() const { return entry_->str.index(); }
+
+const StringPool::Context& StringPool::StyleRef::GetContext() const {
+ return entry_->str.GetContext();
}
-const StringPool::Context& StringPool::StyleRef::getContext() const {
- return mEntry->str.getContext();
+StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
+ return MakeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::makeRef(const StringPiece& str) {
- 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 = indexed_strings_.find(str);
+ if (iter != std::end(indexed_strings_)) {
+ 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 = strings_.size();
+ entry->ref_ = 0;
+ strings_.emplace_back(entry);
+ indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
+ return Ref(entry);
}
-StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
- return makeRef(str, Context{});
+StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
+ 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 = strings_.size();
+ entry->ref_ = 0;
+ strings_.emplace_back(entry);
+ indexed_strings_.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* style_entry = new StyleEntry();
+ style_entry->str = Ref(entry);
+ for (const aapt::Span& span : str.spans) {
+ style_entry->spans.emplace_back(
+ Span{MakeRef(span.name), span.first_char, span.last_char});
+ }
+ style_entry->ref_ = 0;
+ styles_.emplace_back(style_entry);
+ return StyleRef(style_entry);
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) {
+ Entry* entry = new Entry();
+ entry->value = *ref.entry_->str;
+ entry->context = ref.entry_->str.entry_->context;
+ entry->index = strings_.size();
+ entry->ref_ = 0;
+ strings_.emplace_back(entry);
+ indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
+
+ StyleEntry* style_entry = new StyleEntry();
+ style_entry->str = Ref(entry);
+ for (const Span& span : ref.entry_->spans) {
+ style_entry->spans.emplace_back(
+ Span{MakeRef(*span.name), span.first_char, span.last_char});
+ }
+ style_entry->ref_ = 0;
+ styles_.emplace_back(style_entry);
+ return StyleRef(style_entry);
+}
+
+void StringPool::Merge(StringPool&& pool) {
+ indexed_strings_.insert(pool.indexed_strings_.begin(),
+ pool.indexed_strings_.end());
+ pool.indexed_strings_.clear();
+ std::move(pool.strings_.begin(), pool.strings_.end(),
+ std::back_inserter(strings_));
+ pool.strings_.clear();
+ std::move(pool.styles_.begin(), pool.styles_.end(),
+ std::back_inserter(styles_));
+ pool.styles_.clear();
+
+ // Assign the indices.
+ const size_t len = strings_.size();
+ for (size_t index = 0; index < len; index++) {
+ strings_[index]->index = index;
+ }
+}
+
+void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) {
+ strings_.reserve(strings_.size() + stringCount);
+ styles_.reserve(styles_.size() + styleCount);
+}
+
+void StringPool::Prune() {
+ const auto iter_end = indexed_strings_.end();
+ auto index_iter = indexed_strings_.begin();
+ while (index_iter != iter_end) {
+ if (index_iter->second->ref_ <= 0) {
+ index_iter = indexed_strings_.erase(index_iter);
+ } else {
+ ++index_iter;
}
- styleEntry->ref = 0;
- mStyles.emplace_back(styleEntry);
- return StyleRef(styleEntry);
+ }
+
+ auto end_iter2 =
+ std::remove_if(strings_.begin(), strings_.end(),
+ [](const std::unique_ptr<Entry>& entry) -> bool {
+ return entry->ref_ <= 0;
+ });
+
+ auto end_iter3 =
+ std::remove_if(styles_.begin(), styles_.end(),
+ [](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.
+ strings_.erase(end_iter2, strings_.end());
+ styles_.erase(end_iter3, styles_.end());
+
+ // Reassign the indices.
+ const size_t len = strings_.size();
+ for (size_t index = 0; index < len; index++) {
+ strings_[index]->index = index;
+ }
}
-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));
+void StringPool::Sort(
+ const std::function<bool(const Entry&, const Entry&)>& cmp) {
+ std::sort(
+ strings_.begin(), strings_.end(),
+ [&cmp](const std::unique_ptr<Entry>& a,
+ const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); });
- 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);
-}
+ // Assign the indices.
+ const size_t len = strings_.size();
+ for (size_t index = 0; index < len; index++) {
+ strings_[index]->index = index;
+ }
-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();
-
- // 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);
-}
-
-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;
- }
- }
-
- 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;
- }
- );
-
- // 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;
- }
-}
-
-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;
- }
-
- // Reorder the styles.
- std::sort(std::begin(mStyles), std::end(mStyles),
+ // Reorder the styles.
+ std::sort(styles_.begin(), styles_.end(),
[](const std::unique_ptr<StyleEntry>& lhs,
const std::unique_ptr<StyleEntry>& rhs) -> bool {
- return lhs->str.getIndex() < rhs->str.getIndex();
- }
- );
+ return lhs->str.index() < rhs->str.index();
+ });
}
template <typename T>
-static T* encodeLength(T* data, size_t length) {
- static_assert(std::is_integral<T>::value, "wat.");
+static T* EncodeLength(T* data, size_t length) {
+ 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 size_t EncodedLengthUnits(size_t length) {
+ 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 start_index = 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;
+ }
-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();
+ uint32_t* indices =
+ pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr;
+
+ uint32_t* style_indices = nullptr;
+ if (!pool.styles_.empty()) {
+ header->styleCount = pool.styles_.back()->str.index() + 1;
+ style_indices = out->NextBlock<uint32_t>(header->styleCount);
+ }
+
+ const size_t before_strings_index = out->size();
+ header->stringsStart = before_strings_index - start_index;
+
+ for (const auto& entry : pool) {
+ *indices = out->size() - before_strings_index;
+ indices++;
+
if (utf8) {
- header->flags |= android::ResStringPool_header::UTF8_FLAG;
+ const std::string& encoded = entry->value;
+ const ssize_t utf16_length = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(entry->value.data()),
+ entry->value.size());
+ CHECK(utf16_length >= 0);
+
+ const size_t total_size = EncodedLengthUnits<char>(utf16_length) +
+ EncodedLengthUnits<char>(encoded.length()) +
+ encoded.size() + 1;
+
+ char* data = out->NextBlock<char>(total_size);
+
+ // First encode the UTF16 string length.
+ data = EncodeLength(data, utf16_length);
+
+ // 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 utf16_length = encoded.size();
+
+ // Total number of 16-bit words to write.
+ const size_t total_size =
+ EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1;
+
+ char16_t* data = out->NextBlock<char16_t>(total_size);
+
+ // Encode the actual UTF16 string length.
+ data = EncodeLength(data, utf16_length);
+ const size_t byte_length = encoded.size() * sizeof(char16_t);
+
+ // NOTE: For some reason, strncpy16(data, entry->value.data(),
+ // entry->value.size()) truncates the string.
+ memcpy(data, encoded.data(), byte_length);
+
+ // The null-terminating character is already here due to the block of data
+ // being set to 0s on allocation.
+ }
+ }
+
+ out->Align4();
+
+ if (!pool.styles_.empty()) {
+ const size_t before_styles_index = out->size();
+ header->stylesStart = before_styles_index - start_index;
+
+ size_t current_index = 0;
+ for (const auto& entry : pool.styles_) {
+ while (entry->str.index() > current_index) {
+ style_indices[current_index++] = out->size() - before_styles_index;
+
+ uint32_t* span_offset = out->NextBlock<uint32_t>();
+ *span_offset = android::ResStringPool_span::END;
+ }
+ style_indices[current_index++] = out->size() - before_styles_index;
+
+ android::ResStringPool_span* span =
+ out->NextBlock<android::ResStringPool_span>(entry->spans.size());
+ for (const auto& s : entry->spans) {
+ span->name.index = s.name.index();
+ span->firstChar = s.first_char;
+ span->lastChar = s.last_char;
+ 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.
- }
- }
-
- 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;
+ // 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 padding_length = sizeof(android::ResStringPool_span) -
+ sizeof(android::ResStringPool_span::name);
+ uint8_t* padding = out->NextBlock<uint8_t>(padding_length);
+ memset(padding, 0xff, padding_length);
+ out->Align4();
+ }
+ header->header.size = out->size() - start_index;
+ return true;
}
-bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
- return flatten(out, pool, true);
+bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool) {
+ return Flatten(out, pool, true);
}
-bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) {
- return flatten(out, pool, false);
+bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool) {
+ return Flatten(out, pool, false);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 13545be..a4f556c 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/StringPiece.h"
-
#include <functional>
-#include <unordered_map>
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
+#include "ConfigDescription.h"
+#include "util/BigBuffer.h"
+#include "util/StringPiece.h"
+
namespace aapt {
struct Span {
- std::string name;
- uint32_t firstChar;
- uint32_t lastChar;
+ std::string name;
+ uint32_t first_char;
+ uint32_t last_char;
};
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 index() const;
+ const Context& GetContext() const;
- explicit Ref(Entry* entry);
+ private:
+ friend class StringPool;
- Entry* mEntry;
- };
+ explicit Ref(Entry* entry);
- class StyleEntry;
+ Entry* entry_;
+ };
- 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 index() 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* entry_;
+ };
- 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 first_char;
+ uint32_t last_char;
+ };
- 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);
+ /**
+ * Returns 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 string_count, size_t style_count);
- /**
- * 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>> strings_;
+ std::vector<std::unique_ptr<StyleEntry>> styles_;
+ std::unordered_multimap<StringPiece, Entry*> indexed_strings_;
};
//
// Inline implementation
//
-inline size_t StringPool::size() const {
- return mStrings.size();
-}
+inline size_t StringPool::size() const { return strings_.size(); }
inline StringPool::const_iterator begin(const StringPool& pool) {
- return pool.mStrings.begin();
+ return pool.strings_.begin();
}
inline StringPool::const_iterator end(const StringPool& pool) {
- return pool.mStrings.end();
+ return pool.strings_.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..e1394fc 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -15,254 +15,254 @@
*/
#include "StringPool.h"
-#include "test/Test.h"
-#include "util/Util.h"
#include <string>
+#include "test/Test.h"
+#include "util/Util.h"
+
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.index());
+ EXPECT_EQ(1u, ref2.index());
+ EXPECT_EQ(2u, ref3.index());
}
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.index());
- EXPECT_EQ(*(ref2->str), "a");
- EXPECT_EQ(1u, ref2.getIndex());
+ EXPECT_EQ(*(ref2->str), "a");
+ EXPECT_EQ(1u, ref2.index());
- EXPECT_EQ(*ref3, "m");
- EXPECT_EQ(2u, ref3.getIndex());
+ EXPECT_EQ(*ref3, "m");
+ EXPECT_EQ(2u, ref3.index());
- 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.index());
- EXPECT_EQ(*ref, "z");
- EXPECT_EQ(2u, ref.getIndex());
+ EXPECT_EQ(*(ref2->str), "a");
+ EXPECT_EQ(0u, ref2.index());
- 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.index());
}
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.index(), ref.index());
+ EXPECT_EQ(ref5.index(), ref2.index());
+ EXPECT_EQ(ref6.index(), ref3.index());
}
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.index());
+ 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.first_char);
+ EXPECT_EQ(6u, span.last_char);
}
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.index(), styleRef.index());
}
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.index());
+ EXPECT_EQ(1u, ref2.index());
+ EXPECT_EQ(2u, ref3.index());
+ EXPECT_EQ(3u, ref4.index());
+ EXPECT_EQ(4u, ref5.index());
- 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..1cb6aa1 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -17,91 +17,92 @@
#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
- * into compound types. Use ValueVisitor for that.
+ * 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) override { /* 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 +110,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 +120,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));
+const T* ValueCast(const Value* value) {
+ return ValueCast<T>(const_cast<Value*>(value));
}
/**
@@ -138,31 +135,33 @@
* Otherwise, returns nullptr.
*/
template <typename T>
-T* valueCast(Value* value) {
- if (!value) {
- return nullptr;
- }
- DynCastVisitor<T> visitor;
- value->accept(&visitor);
- return visitor.value;
+T* ValueCast(Value* 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& config_value : entry->values) {
+ config_value->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..eb75b10 100644
--- a/tools/aapt2/ValueVisitor_test.cpp
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -14,73 +14,74 @@
* limitations under the License.
*/
-#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "test/Test.h"
-#include "util/Util.h"
#include <string>
+#include "ResourceValues.h"
+#include "test/Test.h"
+#include "util/Util.h"
+
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*> visited_refs;
+ Style* visited_style = nullptr;
- void visit(Reference* ref) override {
- visitedRefs.push_back(ref);
- }
+ void Visit(Reference* ref) override { visited_refs.push_back(ref); }
- void visit(Style* style) override {
- visitedStyle = style;
- ValueVisitor::visit(style);
- }
+ void Visit(Style* style) override {
+ visited_style = 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.visited_style);
- // 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.visited_refs.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 e0f37ec..a06140c 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -14,6 +14,11 @@
* limitations under the License.
*/
+#include <dirent.h>
+
+#include <fstream>
+#include <string>
+
#include "ConfigDescription.h"
#include "Diagnostics.h"
#include "Flags.h"
@@ -33,12 +38,10 @@
#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 <dirent.h>
-#include <fstream>
-#include <string>
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
using google::protobuf::io::CopyingOutputStreamAdaptor;
using google::protobuf::io::ZeroCopyOutputStream;
@@ -46,599 +49,758 @@
namespace aapt {
struct ResourcePathData {
- Source source;
- std::string resourceDir;
- std::string name;
- std::string extension;
+ Source source;
+ std::string resource_dir;
+ 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 config_str;
+ ConfigDescription config;
};
/**
* Resource file paths are expected to look like:
* [--/res/]type[-config]/name
*/
-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 {};
+static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
+ std::string* out_error) {
+ std::vector<std::string> parts = util::Split(path, file::sDirSep);
+ if (parts.size() < 2) {
+ if (out_error) *out_error = "bad resource path";
+ return {};
+ }
+
+ std::string& dir = parts[parts.size() - 2];
+ StringPiece dir_str = dir;
+
+ StringPiece config_str;
+ ConfigDescription config;
+ size_t dash_pos = dir.find('-');
+ if (dash_pos != std::string::npos) {
+ config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1));
+ if (!ConfigDescription::Parse(config_str, &config)) {
+ if (out_error) {
+ std::stringstream err_str;
+ err_str << "invalid configuration '" << config_str << "'";
+ *out_error = err_str.str();
+ }
+ return {};
}
+ dir_str = dir_str.substr(0, dash_pos);
+ }
- std::string& dir = parts[parts.size() - 2];
- StringPiece dirStr = dir;
+ std::string& filename = parts[parts.size() - 1];
+ StringPiece name = filename;
+ StringPiece extension;
+ size_t dot_pos = filename.find('.');
+ if (dot_pos != std::string::npos) {
+ extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1));
+ name = name.substr(0, dot_pos);
+ }
- 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), dir_str.ToString(),
+ name.ToString(), extension.ToString(),
+ config_str.ToString(), config};
}
struct CompileOptions {
- std::string outputPath;
- Maybe<std::string> resDir;
- bool pseudolocalize = false;
- bool legacyMode = false;
- bool verbose = false;
+ std::string output_path;
+ Maybe<std::string> res_dir;
+ bool pseudolocalize = false;
+ bool legacy_mode = 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();
+static std::string BuildIntermediateFilename(const ResourcePathData& data) {
+ std::stringstream name;
+ name << data.resource_dir;
+ if (!data.config_str.empty()) {
+ name << "-" << data.config_str;
+ }
+ 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, ".");
+static bool IsHidden(const StringPiece& filename) {
+ return util::StartsWith(filename, ".");
}
/**
* Walks the res directory structure, looking for resource files.
*/
-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));
+static bool LoadInputFilesFromDir(
+ IAaptContext* context, const CompileOptions& options,
+ std::vector<ResourcePathData>* out_path_data) {
+ const std::string& root_dir = options.res_dir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.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 prefix_path = root_dir;
+ file::AppendPath(&prefix_path, entry->d_name);
+
+ if (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subdir(
+ opendir(prefix_path.data()), closedir);
+ if (!subdir) {
+ context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leaf_entry = readdir(subdir.get())) {
+ if (IsHidden(leaf_entry->d_name)) {
+ continue;
+ }
+
+ std::string full_path = prefix_path;
+ file::AppendPath(&full_path, leaf_entry->d_name);
+
+ std::string err_str;
+ Maybe<ResourcePathData> path_data =
+ ExtractResourcePathData(full_path, &err_str);
+ if (!path_data) {
+ context->GetDiagnostics()->Error(DiagMessage() << err_str);
return false;
+ }
+
+ out_path_data->push_back(std::move(path_data.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 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();
+static bool CompileTable(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer,
+ const std::string& output_path) {
+ ResourceTable table;
+ {
+ std::ifstream fin(path_data.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->GetDiagnostics()->Error(DiagMessage(path_data.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 xml_parser(fin);
+
+ ResourceParserOptions parser_options;
+ parser_options.error_on_positional_arguments = !options.legacy_mode;
+
+ // If the filename includes donottranslate, then the default translatable is
+ // false.
+ parser_options.translatable =
+ path_data.name.find("donottranslate") == std::string::npos;
+
+ ResourceParser res_parser(context->GetDiagnostics(), &table,
+ path_data.source, path_data.config,
+ parser_options);
+ if (!res_parser.Parse(&xml_parser)) {
+ 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 pseudolocale_generator;
+ if (!pseudolocale_generator.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(output_path, 0)) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "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 copying_adaptor(writer);
+
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
+ if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "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(output_path)
+ << "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& output_path,
+ 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(output_path, 0)) {
+ diag->Error(DiagMessage(output_path) << "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 copying_adaptor(writer);
+ CompiledFileOutputStream output_stream(©ing_adaptor);
+
+ // Number of CompiledFiles.
+ output_stream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiled_file =
+ SerializeCompiledFileToPb(file);
+ output_stream.WriteCompiledFile(compiled_file.get());
+ output_stream.WriteData(&buffer);
+
+ if (output_stream.HadError()) {
+ diag->Error(DiagMessage(output_path) << "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(output_path) << "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& output_path,
+ 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(output_path, 0)) {
+ diag->Error(DiagMessage(output_path) << "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 copying_adaptor(writer);
+ CompiledFileOutputStream output_stream(©ing_adaptor);
+
+ // Number of CompiledFiles.
+ output_stream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiled_file =
+ SerializeCompiledFileToPb(file);
+ output_stream.WriteCompiledFile(compiled_file.get());
+ output_stream.WriteData(map.getDataPtr(), map.getDataLength());
+
+ if (output_stream.HadError()) {
+ diag->Error(DiagMessage(output_path) << "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(output_path) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath,
- xml::XmlResource* xmlRes,
+static bool FlattenXmlToOutStream(IAaptContext* context,
+ const StringPiece& output_path,
+ 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 xml_flattener_options;
+ xml_flattener_options.keep_raw_values = true;
+ XmlFlattener flattener(&buffer, xml_flattener_options);
+ 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> pb_compiled_file =
+ SerializeCompiledFileToPb(xmlres->file);
+ out->WriteCompiledFile(pb_compiled_file.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(output_path)
+ << "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) {
+static bool CompileXml(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer, const std::string& output_path) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.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;
- }
-
- xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
-
- fin.close();
+ std::unique_ptr<xml::XmlResource> xmlres;
+ {
+ std::ifstream fin(path_data.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << strerror(errno));
+ return false;
}
- if (!xmlRes) {
+ xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
+
+ fin.close();
+ }
+
+ if (!xmlres) {
+ return false;
+ }
+
+ xmlres->file.name = ResourceName(
+ {}, *ParseResourceType(path_data.resource_dir), path_data.name);
+ xmlres->file.config = path_data.config;
+ xmlres->file.source = path_data.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 inline_xml_format_parser;
+ if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
+ return false;
+ }
+
+ // Start the entry so we can write the header.
+ if (!writer->StartEntry(output_path, 0)) {
+ context->GetDiagnostics()->Error(DiagMessage(output_path)
+ << "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 copying_adaptor(writer);
+ CompiledFileOutputStream output_stream(©ing_adaptor);
+
+ std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
+ inline_xml_format_parser.GetExtractedInlineXmlDocuments();
+
+ // Number of CompiledFiles.
+ output_stream.WriteLittleEndian32(1 + inline_documents.size());
+
+ if (!FlattenXmlToOutStream(context, output_path, xmlres.get(),
+ &output_stream)) {
+ return false;
+ }
+
+ for (auto& inline_xml_doc : inline_documents) {
+ if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(),
+ &output_stream)) {
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(output_path)
+ << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool compilePng(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, IArchiveWriter* writer,
- const std::string& outputPath) {
- BigBuffer buffer(4096);
- ResourceFile resFile;
- resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- resFile.config = pathData.config;
- resFile.source = pathData.source;
+class BigBufferOutputStream : public io::OutputStream {
+ public:
+ explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
- {
- std::ifstream fin(pathData.source.path, std::ifstream::binary);
- if (!fin) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
- return false;
- }
-
- Png png(context->getDiagnostics());
- if (!png.process(pathData.source, &fin, &buffer, {})) {
- return false;
- }
- }
-
- if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
- context->getDiagnostics())) {
- return false;
- }
+ bool Next(void** data, int* len) override {
+ size_t count;
+ *data = buffer_->NextBlock(&count);
+ *len = static_cast<int>(count);
return true;
+ }
+
+ void BackUp(int count) override { buffer_->BackUp(count); }
+
+ int64_t ByteCount() const override { return buffer_->size(); }
+
+ bool HadError() const override { return false; }
+
+ private:
+ BigBuffer* buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+};
+
+static bool CompilePng(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer, const std::string& output_path) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "compiling PNG");
+ }
+
+ BigBuffer buffer(4096);
+ ResourceFile res_file;
+ res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir),
+ path_data.name);
+ res_file.config = path_data.config;
+ res_file.source = path_data.source;
+
+ {
+ std::string content;
+ if (!android::base::ReadFileToString(path_data.source.path, &content)) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(path_data.source)
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+
+ BigBuffer crunched_png_buffer(4096);
+ BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+
+ // Ensure that we only keep the chunks we care about if we end up
+ // using the original PNG instead of the crunched one.
+ PngChunkFilter png_chunk_filter(content);
+ std::unique_ptr<Image> image = ReadPng(context, &png_chunk_filter);
+ if (!image) {
+ return false;
+ }
+
+ std::unique_ptr<NinePatch> nine_patch;
+ if (path_data.extension == "9.png") {
+ std::string err;
+ nine_patch = NinePatch::Create(image->rows.get(), image->width,
+ image->height, &err);
+ if (!nine_patch) {
+ 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->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "9-patch: " << *nine_patch);
+ }
+ }
+
+ // Write the crunched PNG.
+ if (!WritePng(context, image.get(), nine_patch.get(),
+ &crunched_png_buffer_out, {})) {
+ return false;
+ }
+
+ if (nine_patch != nullptr ||
+ crunched_png_buffer_out.ByteCount() <= png_chunk_filter.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(crunched_png_buffer));
+ } else {
+ // The re-encoded PNG is larger than the original, and there is
+ // no mandatory transformation. Use the original.
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(
+ DiagMessage(path_data.source)
+ << "original PNG is smaller than crunched PNG"
+ << ", using original");
+ }
+
+ PngChunkFilter png_chunk_filter_again(content);
+ BigBuffer filtered_png_buffer(4096);
+ BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+ io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again);
+ buffer.AppendBuffer(std::move(filtered_png_buffer));
+ }
+
+ if (context->IsVerbose()) {
+ // 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 legacy_stream(content);
+ BigBuffer legacy_buffer(4096);
+ Png png(context->GetDiagnostics());
+ if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
+ return false;
+ }
+
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "legacy=" << legacy_buffer.size()
+ << " new=" << buffer.size());
+ }
+ }
+
+ if (!WriteHeaderAndBufferToWriter(output_path, res_file, 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) {
- BigBuffer buffer(256);
- ResourceFile resFile;
- resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- resFile.config = pathData.config;
- resFile.source = pathData.source;
+static bool CompileFile(IAaptContext* context, const CompileOptions& options,
+ const ResourcePathData& path_data,
+ IArchiveWriter* writer,
+ const std::string& output_path) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "compiling file");
+ }
- std::string errorStr;
- Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
- if (!f) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
- return false;
- }
+ BigBuffer buffer(256);
+ ResourceFile res_file;
+ res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir),
+ path_data.name);
+ res_file.config = path_data.config;
+ res_file.source = path_data.source;
- if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
- context->getDiagnostics())) {
- return false;
- }
- return true;
+ std::string error_str;
+ Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
+ if (!f) {
+ context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << error_str);
+ return false;
+ }
+
+ if (!WriteHeaderAndMmapToWriter(output_path, res_file, 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) { verbose_ = val; }
- bool verbose() override {
- return mVerbose;
- }
+ bool IsVerbose() override { return verbose_; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- 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 diagnostics_;
+ bool verbose_ = 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;
+int Compile(const std::vector<StringPiece>& args) {
+ 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.output_path)
+ .OptionalFlag("--dir", "Directory to scan for resources",
+ &options.res_dir)
+ .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.legacy_mode)
+ .OptionalSwitch("-v", "Enables verbose logging", &verbose);
+ if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
+ return 1;
+ }
+
+ context.SetVerbose(verbose);
+
+ std::unique_ptr<IArchiveWriter> archive_writer;
+
+ std::vector<ResourcePathData> input_data;
+ if (options.res_dir) {
+ 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, &input_data)) {
+ return 1;
+ }
- std::unique_ptr<IArchiveWriter> archiveWriter;
+ archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
+ options.output_path);
- 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 {
+ input_data.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 error_str;
+ if (Maybe<ResourcePathData> path_data =
+ ExtractResourcePathData(arg, &error_str)) {
+ input_data.push_back(std::move(path_data.value()));
+ } else {
+ context.GetDiagnostics()->Error(DiagMessage() << error_str << " ("
+ << arg << ")");
+ return 1;
+ }
+ }
- archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+ archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(),
+ options.output_path);
+ }
+
+ if (!archive_writer) {
+ return 1;
+ }
+
+ bool error = false;
+ for (ResourcePathData& path_data : input_data) {
+ if (options.verbose) {
+ context.GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "processing");
+ }
+
+ if (path_data.resource_dir == "values") {
+ // Overwrite the extension.
+ path_data.extension = "arsc";
+
+ const std::string output_filename = BuildIntermediateFilename(path_data);
+ if (!CompileTable(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ 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 output_filename = BuildIntermediateFilename(path_data);
+ if (const ResourceType* type =
+ ParseResourceType(path_data.resource_dir)) {
+ if (*type != ResourceType::kRaw) {
+ if (path_data.extension == "xml") {
+ if (!CompileXml(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ 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 (path_data.extension == "png" ||
+ path_data.extension == "9.png") {
+ if (!CompilePng(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ error = true;
}
-
+ } else {
+ if (!CompileFile(&context, options, path_data, archive_writer.get(),
+ output_filename)) {
+ 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, path_data, archive_writer.get(),
+ output_filename)) {
+ error = true;
+ }
}
+ } else {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "invalid file path '" << path_data.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..17c22c5 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -14,189 +14,205 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "compile/IdAssigner.h"
+
+#include <map>
+
+#include "android-base/logging.h"
+
+#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
-#include <cassert>
-#include <map>
-
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.package_id()) {
+ if (!type->id || type->id.value() == id.type_id()) {
+ type->id = id.type_id();
- if (!entry->id || entry->id.value() == id.entryId()) {
- entry->id = id.entryId();
- return true;
- }
- }
+ if (!entry->id || entry->id.value() == id.entry_id()) {
+ entry->id = id.entry_id();
+ 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 existing_id(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 " << existing_id);
+ return false;
}
-bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
- std::map<ResourceId, ResourceName> assignedIds;
+bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
+ std::map<ResourceId, ResourceName> assigned_ids;
- for (auto& package : table->packages) {
- assert(package->id && "packages must have manually assigned IDs");
+ for (auto& package : table->packages) {
+ CHECK(bool(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 (assigned_id_map_) {
+ // Assign the pre-assigned stable ID meant for this resource.
+ const auto iter = assigned_id_map_->find(name);
+ if (iter != assigned_id_map_->end()) {
+ const ResourceId assigned_id = iter->second;
+ const bool result =
+ AssignId(context->GetDiagnostics(), assigned_id, 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 resource_id(package->id.value(), type->id.value(),
+ entry->id.value());
+ auto result = assigned_ids.insert({resource_id, name});
+ const ResourceName& existing_name = result.first->second;
+ if (!result.second) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "resource " << name << " has same ID "
+ << resource_id << " as " << existing_name);
+ return false;
+ }
}
+ }
}
+ }
- // Assign any resources without IDs the next available ID. Gaps will be filled if possible,
- // unless those IDs have been reserved.
+ if (assigned_id_map_) {
+ // 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& stable_id_entry : *assigned_id_map_) {
+ const ResourceName& pre_assigned_name = stable_id_entry.first;
+ const ResourceId& pre_assigned_id = stable_id_entry.second;
+ auto result = assigned_ids.insert({pre_assigned_id, pre_assigned_name});
+ const ResourceName& existing_name = result.first->second;
+ if (!result.second && existing_name != pre_assigned_name) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "stable ID " << pre_assigned_id << " for resource "
+ << pre_assigned_name
+ << " is already taken by resource " << existing_name);
+ 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 assigned_ids_iter_end = assigned_ids.end();
+ for (auto& package : table->packages) {
+ CHECK(bool(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 resource_id(package->id.value(), 0, 0);
+ uint8_t next_expected_type_id = 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 next_type_iter = assigned_ids.lower_bound(resource_id);
+ 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 (next_type_iter != assigned_ids_iter_end) {
+ if (next_type_iter->first.package_id() != 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 type_id = next_type_iter->first.type_id();
+ if (type_id > next_expected_type_id) {
+ // There is a gap in the type IDs, so use the missing one.
+ type->id = next_expected_type_id++;
+ break;
+ }
- // Move to the next reserved ID.
- ++nextTypeIter;
- }
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ next_expected_type_id = type_id + 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.
+ ++next_type_iter;
}
+
+ 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 = next_expected_type_id++;
+ }
+ }
+
+ resource_id = ResourceId(package->id.value(), type->id.value(), 0);
+ uint16_t next_expected_entry_id = 0;
+
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package
+ // and type set.
+ auto next_entry_iter = assigned_ids.lower_bound(resource_id);
+ 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 (next_entry_iter != assigned_ids_iter_end) {
+ if (next_entry_iter->first.package_id() != package->id.value() ||
+ next_entry_iter->first.type_id() != type->id.value()) {
+ break;
+ }
+
+ const uint16_t entry_id = next_entry_iter->first.entry_id();
+ if (entry_id > next_expected_entry_id) {
+ // There is a gap in the entry IDs, so use the missing one.
+ entry->id = next_expected_entry_id++;
+ break;
+ }
+
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ next_expected_entry_id = entry_id + 1;
+
+ // Move to the next reserved entry ID.
+ ++next_entry_iter;
+ }
+
+ 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 = next_expected_entry_id++;
+ }
+ }
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 06cd5e3..371ec01 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -17,31 +17,33 @@
#ifndef AAPT_COMPILE_IDASSIGNER_H
#define AAPT_COMPILE_IDASSIGNER_H
+#include <unordered_map>
+
#include "Resource.h"
#include "process/IResourceTableConsumer.h"
-#include <android-base/macros.h>
-#include <unordered_map>
+#include "android-base/macros.h"
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)
+ : assigned_id_map_(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>* assigned_id_map_ =
+ 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..d465091 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -15,161 +15,171 @@
*/
#include "compile/IdAssigner.h"
+
#include "test/Test.h"
namespace aapt {
-::testing::AssertionResult verifyIds(ResourceTable* table);
+::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> maybe_result;
- // 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);
+ maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint8_t>(2), maybe_result.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);
+ maybe_result =
+ table->FindResource(test::ParseNameOrDie("android:integer/three"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint8_t>(3), maybe_result.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);
+ maybe_result =
+ table->FindResource(test::ParseNameOrDie("android:string/five"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint8_t>(5), maybe_result.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);
+ maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint16_t>(1), maybe_result.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);
+ maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz"));
+ AAPT_ASSERT_TRUE(maybe_result);
+ EXPECT_EQ(make_value<uint16_t>(2), maybe_result.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> id_map = {
+ {test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
+ IdAssigner assigner(&id_map);
+ 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& search_result = result.value();
+ EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id);
+ EXPECT_EQ(make_value<uint8_t>(0x01), search_result.type->id);
+ EXPECT_EQ(make_value<uint16_t>(0x0002), search_result.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;
- }
+::testing::AssertionResult VerifyIds(ResourceTable* table) {
+ std::set<uint8_t> package_ids;
+ 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 (!package_ids.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> type_ids;
+ for (auto& type : package->types) {
+ if (!type->id) {
+ return ::testing::AssertionFailure() << "type " << type->type
+ << " of package " << package->name
+ << " has no ID";
+ }
+
+ if (!type_ids.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> entry_ids;
+ 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 (!entry_ids.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
new file mode 100644
index 0000000..db0b945
--- /dev/null
+++ b/tools/aapt2/compile/Image.h
@@ -0,0 +1,209 @@
+/*
+ * 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_COMPILE_IMAGE_H
+#define AAPT_COMPILE_IMAGE_H
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+namespace aapt {
+
+/**
+ * An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
+ */
+class Image {
+ 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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);
+};
+
+/**
+ * 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;
+
+ 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;
+}
+
+/**
+ * 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;
+
+ 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;
+};
+
+inline bool Bounds::nonZero() const {
+ 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;
+}
+
+/**
+ * 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* err_out);
+
+ /**
+ * 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;
+
+ /**
+ * 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 layout_bounds;
+
+ /**
+ * 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 outline_radius = 0.0f;
+
+ /**
+ * The largest alpha value within the outline.
+ */
+ uint32_t outline_alpha = 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> horizontal_stretch_regions;
+
+ /**
+ * 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> vertical_stretch_regions;
+
+ /**
+ * 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> region_colors;
+
+ /**
+ * 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* out_len) const;
+
+ /**
+ * Serializes the layout bounds.
+ */
+ std::unique_ptr<uint8_t[]> SerializeLayoutBounds(size_t* out_len) const;
+
+ /**
+ * Serializes the rounded-rect outline.
+ */
+ std::unique_ptr<uint8_t[]> SerializeRoundedRectOutline(size_t* out_len) const;
+
+ private:
+ explicit NinePatch() = default;
+
+ 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& nine_patch);
+
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_IMAGE_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
index f965bff..786494b 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -14,17 +14,19 @@
* limitations under the License.
*/
+#include "compile/InlineXmlFormatParser.h"
+
+#include <sstream>
+#include <string>
+
+#include "android-base/macros.h"
+
#include "Debug.h"
#include "ResourceUtils.h"
-#include "compile/InlineXmlFormatParser.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
#include "xml/XmlUtil.h"
-#include <android-base/macros.h>
-#include <sstream>
-#include <string>
-
namespace aapt {
namespace {
@@ -33,158 +35,173 @@
* 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 attr_namespace_uri;
+ std::string attr_name;
+ };
- explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) :
- mContext(context), mXmlResource(xmlResource) {
+ explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
+ : context_(context), xml_resource_(xml_resource) {}
+
+ void Visit(xml::Element* el) override {
+ if (el->namespace_uri != 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 = xml_resource_->file.source.WithLine(el->line_number);
- 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) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "missing 'name' attribute");
+ error_ = true;
+ return;
}
- const std::vector<InlineDeclaration>& getInlineDeclarations() const {
- return mInlineDeclarations;
+ Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
+ if (!ref) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
+ error_ = 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> maybe_pkg =
+ TransformPackageAlias(name.package, {});
+ if (!maybe_pkg) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "invalid namespace prefix '"
+ << name.package << "'");
+ error_ = true;
+ return;
}
-private:
- DISALLOW_COPY_AND_ASSIGN(Visitor);
+ const xml::ExtractedPackage& pkg = maybe_pkg.value();
+ const bool private_namespace =
+ pkg.private_namespace || ref.value().private_reference;
- IAaptContext* mContext;
- xml::XmlResource* mXmlResource;
- std::vector<InlineDeclaration> mInlineDeclarations;
- bool mError = false;
+ InlineDeclaration decl;
+ decl.el = el;
+ decl.attr_name = name.entry;
+ if (!pkg.package.empty()) {
+ decl.attr_namespace_uri =
+ xml::BuildPackageNamespace(pkg.package, private_namespace);
+ }
+
+ inline_declarations_.push_back(std::move(decl));
+ }
+
+ const std::vector<InlineDeclaration>& GetInlineDeclarations() const {
+ return inline_declarations_;
+ }
+
+ bool HasError() const { return error_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+ IAaptContext* context_;
+ xml::XmlResource* xml_resource_;
+ std::vector<InlineDeclaration> inline_declarations_;
+ bool error_ = 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 name_suffix_counter = 0;
+ for (const Visitor::InlineDeclaration& decl :
+ visitor.GetInlineDeclarations()) {
+ auto new_doc = util::make_unique<xml::XmlResource>();
+ new_doc->file.config = doc->file.config;
+ new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
+ new_doc->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.
+
+ new_doc->file.name.entry =
+ NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" +
+ std::to_string(name_suffix_counter));
+
+ // 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 child_source = doc->file.source.WithLine(child->line_number);
+ if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {
+ if (!util::TrimWhitespace(t->text).empty()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(child_source)
+ << "can't extract text into its own resource");
+ return false;
+ }
+ } else if (new_doc->root) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(child_source)
+ << "inline XML resources must have a single root");
return false;
+ } else {
+ new_doc->root = std::move(child);
+ new_doc->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* parent_el = nullptr;
+ while (node->parent &&
+ (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) {
+ node = node->parent;
}
- return true;
+
+ if (!parent_el) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(new_doc->file.source)
+ << "no suitable parent for inheriting attribute");
+ return false;
+ }
+
+ // Add the inline attribute to the parent.
+ parent_el->attributes.push_back(
+ xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
+ "@" + new_doc->file.name.ToString()});
+
+ // Delete the subtree.
+ for (auto iter = parent_el->children.begin();
+ iter != parent_el->children.end(); ++iter) {
+ if (iter->get() == node) {
+ parent_el->children.erase(iter);
+ break;
+ }
+ }
+
+ queue_.push_back(std::move(new_doc));
+
+ name_suffix_counter++;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
index 69065fd..1a658fd 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.h
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -17,12 +17,13 @@
#ifndef AAPT_COMPILE_INLINEXMLFORMATPARSER_H
#define AAPT_COMPILE_INLINEXMLFORMATPARSER_H
-#include "process/IResourceTableConsumer.h"
-
-#include <android-base/macros.h>
#include <memory>
#include <vector>
+#include "android-base/macros.h"
+
+#include "process/IResourceTableConsumer.h"
+
namespace aapt {
/**
@@ -41,25 +42,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 queue_;
+ }
-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>> queue_;
};
-} // 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..348796c 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -15,27 +15,28 @@
*/
#include "compile/InlineXmlFormatParser.h"
+
#include "test/Test.h"
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 +46,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 name_ref;
+ ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref));
- xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get();
- ASSERT_NE(nullptr, extractedDoc);
+ xml::XmlResource* extracted_doc =
+ parser.GetExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extracted_doc);
- // 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(name_ref.package, extracted_doc->file.name.package);
+ EXPECT_EQ(name_ref.type, extracted_doc->file.name.type);
+ EXPECT_EQ(name_ref.entry, extracted_doc->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(extracted_doc);
+ 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 +101,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* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attr_text);
- xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable");
- ASSERT_NE(nullptr, attrDrawable);
+ xml::Attribute* attr_drawable =
+ el->FindAttribute(xml::kSchemaAndroid, "drawable");
+ ASSERT_NE(nullptr, attr_drawable);
- // The two extracted resources should have different names.
- EXPECT_NE(attrText->value, attrDrawable->value);
+ // The two extracted resources should have different names.
+ EXPECT_NE(attr_text->value, attr_drawable->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* extracted_doc_text =
+ parser.GetExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extracted_doc_text);
+ el = xml::FindRootElement(extracted_doc_text);
+ 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* extracted_doc_drawable =
+ parser.GetExtractedInlineXmlDocuments()[1].get();
+ ASSERT_NE(nullptr, extracted_doc_drawable);
+ el = xml::FindRootElement(extracted_doc_drawable);
+ 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
new file mode 100644
index 0000000..eab5c97
--- /dev/null
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -0,0 +1,698 @@
+/*
+ * 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 "compile/Image.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+
+#include "util/StringPiece.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+// 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 kPrimaryColor = kColorOpaqueBlack;
+constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
+
+/**
+ * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
+ */
+static uint32_t get_alpha(uint32_t color);
+
+/**
+ * Determines whether a color on an ImageLine is valid.
+ * A 9patch image may use a transparent color as neutral,
+ * or a fully opaque white color as neutral, based on the
+ * pixel color at (0,0) of the image. One or the other is fine,
+ * but we need to ensure consistency throughout the image.
+ */
+class ColorValidator {
+ 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 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,
+// 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
+// were polymorphic:
+//
+// class ImageLine {
+// public:
+// virtual int32_t GetLength() const = 0;
+// virtual uint32_t GetColor(int32_t idx) const = 0;
+// };
+//
+template <typename ImageLine>
+static bool FillRanges(const ImageLine* image_line,
+ const ColorValidator* color_validator,
+ std::vector<Range>* primary_ranges,
+ std::vector<Range>* secondary_ranges,
+ std::string* out_err) {
+ const int32_t length = image_line->GetLength();
+
+ uint32_t last_color = 0xffffffffu;
+ for (int32_t idx = 1; idx < length - 1; idx++) {
+ const uint32_t color = image_line->GetColor(idx);
+ if (!color_validator->IsValidColor(color)) {
+ *out_err = "found an invalid color";
+ return false;
+ }
+
+ if (color != last_color) {
+ // We are ending a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (last_color == kPrimaryColor) {
+ primary_ranges->back().end = idx - 1;
+ } else if (last_color == kSecondaryColor) {
+ secondary_ranges->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) {
+ primary_ranges->push_back(Range(idx - 1, length - 2));
+ } else if (color == kSecondaryColor) {
+ secondary_ranges->push_back(Range(idx - 1, length - 2));
+ }
+ last_color = color;
+ }
+ }
+ return true;
+}
+
+/**
+ * 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)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
+
+ inline int32_t GetLength() const { return length_; }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
+};
+
+/**
+ * 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)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
+
+ inline int32_t GetLength() const { return length_; }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, length_;
+
+ 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)
+ : rows_(rows),
+ xoffset_(xoffset),
+ yoffset_(yoffset),
+ xstep_(xstep),
+ ystep_(ystep),
+ length_(length) {}
+
+ inline int32_t GetLength() const { return length_; }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
+ ((idx + xoffset_) * xstep_) * 4);
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
+};
+
+class TransparentNeutralColorValidator : public ColorValidator {
+ public:
+ bool IsNeutralColor(uint32_t color) const override {
+ return get_alpha(color) == 0;
+ }
+};
+
+class WhiteNeutralColorValidator : public ColorValidator {
+ public:
+ bool IsNeutralColor(uint32_t color) const override {
+ return color == kColorOpaqueWhite;
+ }
+};
+
+inline static uint32_t get_alpha(uint32_t color) {
+ return (color & 0xff000000u) >> 24;
+}
+
+static bool PopulateBounds(const std::vector<Range>& padding,
+ const std::vector<Range>& layout_bounds,
+ const std::vector<Range>& stretch_regions,
+ const int32_t length, int32_t* padding_start,
+ int32_t* padding_end, int32_t* layout_start,
+ int32_t* layout_end, const StringPiece& edge_name,
+ std::string* out_err) {
+ if (padding.size() > 1) {
+ std::stringstream err_stream;
+ err_stream << "too many padding sections on " << edge_name << " border";
+ *out_err = err_stream.str();
+ return false;
+ }
+
+ *padding_start = 0;
+ *padding_end = 0;
+ if (!padding.empty()) {
+ const Range& range = padding.front();
+ *padding_start = range.start;
+ *padding_end = length - range.end;
+ } else if (!stretch_regions.empty()) {
+ // No padding was defined. Compute the padding from the first and last
+ // stretch regions.
+ *padding_start = stretch_regions.front().start;
+ *padding_end = length - stretch_regions.back().end;
+ }
+
+ if (layout_bounds.size() > 2) {
+ std::stringstream err_stream;
+ err_stream << "too many layout bounds sections on " << edge_name
+ << " border";
+ *out_err = err_stream.str();
+ return false;
+ }
+
+ *layout_start = 0;
+ *layout_end = 0;
+ if (layout_bounds.size() >= 1) {
+ const Range& range = layout_bounds.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 err_stream;
+ err_stream << "layout bounds on " << edge_name
+ << " border must start at edge";
+ *out_err = err_stream.str();
+ return false;
+ }
+ *layout_start = range.end;
+
+ if (layout_bounds.size() >= 2) {
+ const Range& range = layout_bounds.back();
+ if (range.end != length) {
+ std::stringstream err_stream;
+ err_stream << "layout bounds on " << edge_name
+ << " border must start at edge";
+ *out_err = err_stream.str();
+ return false;
+ }
+ *layout_end = length - range.start;
+ }
+ }
+ return true;
+}
+
+static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
+ int32_t length) {
+ if (stretch_regions.size() == 0) {
+ return 0;
+ }
+
+ const bool start_is_fixed = stretch_regions.front().start != 0;
+ const bool end_is_fixed = stretch_regions.back().end != length;
+ int32_t modifier = 0;
+ if (start_is_fixed && end_is_fixed) {
+ modifier = 1;
+ } else if (!start_is_fixed && !end_is_fixed) {
+ modifier = -1;
+ }
+ return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
+}
+
+static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
+ // Sample the first pixel to compare against.
+ const uint32_t expected_color =
+ 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 (get_alpha(color) == 0) {
+ // The color is transparent.
+ // If the expectedColor is not transparent, NO_COLOR.
+ if (get_alpha(expected_color) != 0) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ } else if (color != expected_color) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ }
+ }
+
+ if (get_alpha(expected_color) == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return expected_color;
+}
+
+// Fills out_colors with each 9-patch section's color. If the whole section is
+// transparent,
+// it gets the special TRANSPARENT color. If the whole section is the same
+// color, it is assigned
+// that color. Otherwise it gets the special NO_COLOR color.
+//
+// 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>& horizontal_stretch_regions,
+ const std::vector<Range>& vertical_stretch_regions, const int32_t width,
+ const int32_t height, std::vector<uint32_t>* out_colors) {
+ int32_t next_top = 0;
+ Bounds bounds;
+ auto row_iter = vertical_stretch_regions.begin();
+ while (next_top != height) {
+ if (row_iter != vertical_stretch_regions.end()) {
+ if (next_top != row_iter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = next_top + 1;
+ bounds.bottom = row_iter->start + 1;
+ next_top = row_iter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = row_iter->start + 1;
+ bounds.bottom = row_iter->end + 1;
+ next_top = row_iter->end;
+ ++row_iter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = next_top + 1;
+ bounds.bottom = height + 1;
+ next_top = height;
+ }
+
+ int32_t next_left = 0;
+ auto col_iter = horizontal_stretch_regions.begin();
+ while (next_left != width) {
+ if (col_iter != horizontal_stretch_regions.end()) {
+ if (next_left != col_iter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = next_left + 1;
+ bounds.right = col_iter->start + 1;
+ next_left = col_iter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = col_iter->start + 1;
+ bounds.right = col_iter->end + 1;
+ next_left = col_iter->end;
+ ++col_iter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = next_left + 1;
+ bounds.right = width + 1;
+ next_left = width;
+ }
+ out_colors->push_back(GetRegionColor(rows, bounds));
+ }
+ }
+}
+
+// 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* image_line, int32_t* out_start,
+ int32_t* out_end) {
+ *out_start = 0;
+ *out_end = 0;
+
+ const int32_t length = image_line->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 max_alpha = 0;
+ for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
+ uint32_t alpha = get_alpha(image_line->GetColor(i));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ *out_start = i;
+ }
+ }
+
+ max_alpha = 0;
+ for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
+ uint32_t alpha = get_alpha(image_line->GetColor(i));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ *out_end = length - (i + 1);
+ }
+ }
+ return;
+}
+
+template <typename ImageLine>
+static uint32_t FindMaxAlpha(const ImageLine* image_line) {
+ const int32_t length = image_line->GetLength();
+ uint32_t max_alpha = 0;
+ for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
+ uint32_t alpha = get_alpha(image_line->GetColor(idx));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ }
+ }
+ return max_alpha;
+}
+
+// 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];
+}
+
+std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
+ const int32_t width,
+ const int32_t height,
+ std::string* out_err) {
+ if (width < 3 || height < 3) {
+ *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
+ return {};
+ }
+
+ std::vector<Range> horizontal_padding;
+ std::vector<Range> horizontal_layout_bounds;
+ std::vector<Range> vertical_padding;
+ std::vector<Range> vertical_layout_bounds;
+ std::vector<Range> unexpected_ranges;
+ std::unique_ptr<ColorValidator> color_validator;
+
+ if (rows[0][3] == 0) {
+ color_validator = util::make_unique<TransparentNeutralColorValidator>();
+ } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
+ color_validator = util::make_unique<WhiteNeutralColorValidator>();
+ } else {
+ *out_err =
+ "top-left corner pixel must be either opaque white or transparent";
+ return {};
+ }
+
+ // Private constructor, can't use make_unique.
+ auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
+
+ HorizontalImageLine top_row(rows, 0, 0, width);
+ if (!FillRanges(&top_row, color_validator.get(),
+ &nine_patch->horizontal_stretch_regions, &unexpected_ranges,
+ out_err)) {
+ return {};
+ }
+
+ if (!unexpected_ranges.empty()) {
+ const Range& range = unexpected_ranges[0];
+ std::stringstream err_stream;
+ err_stream << "found unexpected optical bounds (red pixel) on top border "
+ << "at x=" << range.start + 1;
+ *out_err = err_stream.str();
+ return {};
+ }
+
+ VerticalImageLine left_col(rows, 0, 0, height);
+ if (!FillRanges(&left_col, color_validator.get(),
+ &nine_patch->vertical_stretch_regions, &unexpected_ranges,
+ out_err)) {
+ return {};
+ }
+
+ if (!unexpected_ranges.empty()) {
+ const Range& range = unexpected_ranges[0];
+ std::stringstream err_stream;
+ err_stream << "found unexpected optical bounds (red pixel) on left border "
+ << "at y=" << range.start + 1;
+ return {};
+ }
+
+ HorizontalImageLine bottom_row(rows, 0, height - 1, width);
+ if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
+ &horizontal_layout_bounds, out_err)) {
+ return {};
+ }
+
+ if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
+ nine_patch->horizontal_stretch_regions, width - 2,
+ &nine_patch->padding.left, &nine_patch->padding.right,
+ &nine_patch->layout_bounds.left,
+ &nine_patch->layout_bounds.right, "bottom", out_err)) {
+ return {};
+ }
+
+ VerticalImageLine right_col(rows, width - 1, 0, height);
+ if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
+ &vertical_layout_bounds, out_err)) {
+ return {};
+ }
+
+ if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
+ nine_patch->vertical_stretch_regions, height - 2,
+ &nine_patch->padding.top, &nine_patch->padding.bottom,
+ &nine_patch->layout_bounds.top,
+ &nine_patch->layout_bounds.bottom, "right", out_err)) {
+ return {};
+ }
+
+ // Fill the region colors of the 9-patch.
+ const int32_t num_rows =
+ CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
+ const int32_t num_cols =
+ CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
+ if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
+ *out_err = "too many regions in 9-patch";
+ return {};
+ }
+
+ nine_patch->region_colors.reserve(num_rows * num_cols);
+ CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
+ nine_patch->vertical_stretch_regions, width - 2,
+ height - 2, &nine_patch->region_colors);
+
+ // Compute the outline based on opacity.
+
+ // Find left and right extent of 9-patch content on center row.
+ HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
+ FindOutlineInsets(&mid_row, &nine_patch->outline.left,
+ &nine_patch->outline.right);
+
+ // Find top and bottom extent of 9-patch content on center column.
+ VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
+ FindOutlineInsets(&mid_col, &nine_patch->outline.top,
+ &nine_patch->outline.bottom);
+
+ const int32_t outline_width =
+ (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
+ const int32_t outline_height =
+ (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
+
+ // Find the largest alpha value within the outline area.
+ HorizontalImageLine outline_mid_row(
+ rows, 1 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top + (outline_height / 2), outline_width);
+ VerticalImageLine outline_mid_col(
+ rows, 1 + nine_patch->outline.left + (outline_width / 2),
+ 1 + nine_patch->outline.top, outline_height);
+ nine_patch->outline_alpha =
+ std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
+
+ // 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 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top, 1, 1,
+ std::min(outline_width, outline_height));
+ int32_t top_left, bottom_right;
+ FindOutlineInsets(&diagonal, &top_left, &bottom_right);
+
+ /* 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
+ */
+ nine_patch->outline_radius = 3.4142f * top_left;
+ return nine_patch;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
+ android::Res_png_9patch data;
+ data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
+ data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
+ data.numColors = static_cast<uint8_t>(region_colors.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*)horizontal_stretch_regions.data(),
+ (const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
+ buffer.get());
+ // Convert to file endianness.
+ reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
+
+ *outLen = data.serializedSize();
+ return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
+ size_t* out_len) const {
+ size_t chunk_len = sizeof(uint32_t) * 4;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+ uint8_t* cursor = buffer.get();
+
+ memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
+ cursor += sizeof(layout_bounds.left);
+
+ memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
+ cursor += sizeof(layout_bounds.top);
+
+ memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
+ cursor += sizeof(layout_bounds.right);
+
+ memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
+ cursor += sizeof(layout_bounds.bottom);
+
+ *out_len = chunk_len;
+ return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
+ size_t* out_len) const {
+ size_t chunk_len = sizeof(uint32_t) * 6;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+ uint8_t* cursor = buffer.get();
+
+ 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.right, sizeof(outline.right));
+ cursor += sizeof(outline.right);
+
+ memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
+ cursor += sizeof(outline.bottom);
+
+ *((float*)cursor) = outline_radius;
+ cursor += sizeof(outline_radius);
+
+ *((uint32_t*)cursor) = outline_alpha;
+
+ *out_len = chunk_len;
+ return buffer;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range) {
+ 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;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
+ return out << "horizontalStretch:"
+ << util::Joiner(nine_patch.horizontal_stretch_regions, " ")
+ << " verticalStretch:"
+ << util::Joiner(nine_patch.vertical_stretch_regions, " ")
+ << " padding: " << nine_patch.padding
+ << ", bounds: " << nine_patch.layout_bounds
+ << ", outline: " << nine_patch.outline
+ << " rad=" << nine_patch.outline_radius
+ << " alpha=" << nine_patch.outline_alpha;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp
new file mode 100644
index 0000000..f54bb2e
--- /dev/null
+++ b/tools/aapt2/compile/NinePatch_test.cpp
@@ -0,0 +1,377 @@
+/*
+ * 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 "compile/Image.h"
+
+#include "test/Test.h"
+
+namespace aapt {
+
+// Pixels are in RGBA_8888 packing.
+
+#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"
+#define GR_20 "\xff\x00\x00\x33"
+#define BLACK "\x00\x00\x00\xff"
+#define WHITE "\xff\xff\xff\xff"
+#define TRANS "\x00\x00\x00\x00"
+
+static uint8_t* k2x2[] = {
+ (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,
+};
+
+static uint8_t* kTransparentNeutralColor3x3[] = {
+ (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,
+};
+
+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,
+};
+
+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,
+};
+
+static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
+ (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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+TEST(NinePatchTest, Minimum3x3) {
+ 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());
+}
+
+TEST(NinePatchTest, TransparentNeutralColor) {
+ std::string err;
+ EXPECT_NE(nullptr,
+ NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
+}
+
+TEST(NinePatchTest, SingleStretchRegion) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
+ ASSERT_EQ(1u, nine_patch->vertical_stretch_regions.size());
+
+ EXPECT_EQ(Range(1, 4), nine_patch->horizontal_stretch_regions.front());
+ EXPECT_EQ(Range(1, 3), nine_patch->vertical_stretch_regions.front());
+}
+
+TEST(NinePatchTest, MultipleStretchRegions) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
+ ASSERT_EQ(2u, nine_patch->vertical_stretch_regions.size());
+
+ EXPECT_EQ(Range(1, 2), nine_patch->horizontal_stretch_regions[0]);
+ EXPECT_EQ(Range(3, 5), nine_patch->horizontal_stretch_regions[1]);
+ EXPECT_EQ(Range(6, 7), nine_patch->horizontal_stretch_regions[2]);
+
+ EXPECT_EQ(Range(0, 2), nine_patch->vertical_stretch_regions[0]);
+ EXPECT_EQ(Range(3, 5), nine_patch->vertical_stretch_regions[1]);
+}
+
+TEST(NinePatchTest, InferPaddingFromStretchRegions) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding);
+}
+
+TEST(NinePatchTest, Padding) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kPadding6x5, 6, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+}
+
+TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
+ 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());
+}
+
+TEST(NinePatchTest, LayoutBounds) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+
+ nine_patch = NinePatch::Create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 0, 0), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, PaddingAndLayoutBounds) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, RegionColorsAreCorrect) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ std::vector<uint32_t> expected_colors = {
+ 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(expected_colors, nine_patch->region_colors);
+}
+
+TEST(NinePatchTest, OutlineFromOpaqueImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
+ EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromTranslucentImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
+ EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromOffCenterImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ // 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 behavior I'm breaking, I will leave it at the
+ // incorrect:
+ EXPECT_EQ(Bounds(4, 3, 3, 3), nine_patch->outline);
+
+ EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineRadius) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
+ EXPECT_EQ(3.4142f, nine_patch->outline_radius);
+}
+
+::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";
+}
+
+TEST(NinePatchTest, SerializePngEndianness) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ size_t len;
+ std::unique_ptr<uint8_t[]> data = nine_patch->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;
+
+ // 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
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 055d8b5..f1bc53e 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);
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ 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 (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;
}
- 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_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);
+ }
- png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+ 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()
- << "writing image: w = " << info->width
- << ", h = " << info->height);
+ 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_color rgbPalette[256];
- png_byte alphaPalette[256];
- bool hasTransparency;
- int paletteEntries;
-
- 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;
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
}
-
- 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;
- }
- }
-
- 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);
- }
-
- 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::EndsWith(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.grayscale_tolerance)) {
+ 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 f835b06e..01c9adb 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -17,31 +17,81 @@
#ifndef AAPT_PNG_H
#define AAPT_PNG_H
-#include "util/BigBuffer.h"
-#include "Diagnostics.h"
-#include "Source.h"
-
#include <iostream>
#include <string>
+#include "android-base/macros.h"
+
+#include "Diagnostics.h"
+#include "Source.h"
+#include "compile/Image.h"
+#include "io/Io.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+
namespace aapt {
struct PngOptions {
- int grayScaleTolerance = 0;
+ int grayscale_tolerance = 0;
};
+/**
+ * Deprecated. Removing once new PNG crunching code is proved to be correct.
+ */
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);
};
-} // namespace aapt
+/**
+ * An InputStream that filters out unimportant PNG chunks.
+ */
+class PngChunkFilter : public io::InputStream {
+ public:
+ explicit PngChunkFilter(const StringPiece& data);
-#endif // AAPT_PNG_H
+ 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>(window_start_);
+ }
+
+ bool HadError() const override { return error_; }
+
+ private:
+ bool ConsumeWindow(const void** buffer, int* len);
+
+ StringPiece data_;
+ size_t window_start_ = 0;
+ size_t window_end_ = 0;
+ bool error_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+};
+
+/**
+ * Reads a PNG from the InputStream into memory as an RGBA Image.
+ */
+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.
+ */
+bool WritePng(IAaptContext* context, const Image* image,
+ const NinePatch* nine_patch, io::OutputStream* out,
+ const PngOptions& options);
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
new file mode 100644
index 0000000..4cbefb9
--- /dev/null
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -0,0 +1,174 @@
+/*
+ * 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 "compile/Png.h"
+
+#include "io/Io.h"
+#include "util/StringPiece.h"
+
+namespace aapt {
+
+static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
+
+// 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);
+}
+
+// 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),
+};
+
+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;
+}
+
+static bool IsPngChunkWhitelisted(uint32_t type) {
+ switch (type) {
+ case kPngChunkIHDR:
+ case kPngChunkIDAT:
+ case kPngChunkIEND:
+ case kPngChunkPLTE:
+ case kPngChunktRNS:
+ case kPngChunksRGB:
+ return true;
+ default:
+ return false;
+ }
+}
+
+PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
+ if (util::StartsWith(data_, kPngSignature)) {
+ window_start_ = 0;
+ window_end_ = strlen(kPngSignature);
+ } else {
+ error_ = true;
+ }
+}
+
+bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) {
+ if (window_start_ != window_end_) {
+ // We have bytes to give from our window.
+ const int bytes_read = (int)(window_end_ - window_start_);
+ *buffer = data_.data() + window_start_;
+ *len = bytes_read;
+ window_start_ = window_end_;
+ return true;
+ }
+ return false;
+}
+
+bool PngChunkFilter::Next(const void** buffer, int* len) {
+ if (error_) {
+ 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 (window_end_ < data_.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 (data_.size() - window_start_ < kMinChunkHeaderSize) {
+ error_ = true;
+ return false;
+ }
+
+ // Verify the chunk length.
+ const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
+ if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) >
+ data_.size()) {
+ // Overflow.
+ error_ = true;
+ return false;
+ }
+
+ // Do we strip this chunk?
+ const uint32_t chunk_type =
+ Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+ if (IsPngChunkWhitelisted(chunk_type)) {
+ // Advance the window to include this chunk.
+ window_end_ += kMinChunkHeaderSize + chunk_len;
+ } else {
+ // We want to strip this chunk. If we accumulated a window,
+ // we must return the window now.
+ if (window_start_ != window_end_) {
+ break;
+ }
+
+ // The window is empty, so we can advance past this chunk
+ // and keep looking for the next good chunk,
+ window_end_ += kMinChunkHeaderSize + chunk_len;
+ window_start_ = window_end_;
+ }
+ }
+
+ if (ConsumeWindow(buffer, len)) {
+ return true;
+ }
+ return false;
+}
+
+void PngChunkFilter::BackUp(int count) {
+ if (error_) {
+ return;
+ }
+ window_start_ -= count;
+}
+
+bool PngChunkFilter::Skip(int count) {
+ if (error_) {
+ 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;
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
new file mode 100644
index 0000000..3b46d8b
--- /dev/null
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -0,0 +1,767 @@
+/*
+ * 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 "compile/Png.h"
+
+#include <png.h>
+#include <zlib.h>
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
+namespace aapt {
+
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
+/**
+ * Custom deleter that destroys libpng read and info structs.
+ */
+class PngReadStructDeleter {
+ public:
+ PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
+ : read_ptr_(read_ptr), info_ptr_(info_ptr) {}
+
+ ~PngReadStructDeleter() {
+ png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
+ }
+
+ private:
+ png_structp read_ptr_;
+ png_infop info_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
+};
+
+/**
+ * Custom deleter that destroys libpng write and info structs.
+ */
+class PngWriteStructDeleter {
+ public:
+ PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
+ : write_ptr_(write_ptr), info_ptr_(info_ptr) {}
+
+ ~PngWriteStructDeleter() {
+ png_destroy_write_struct(&write_ptr_, &info_ptr_);
+ }
+
+ private:
+ png_structp write_ptr_;
+ png_infop info_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
+};
+
+// Custom warning logging method that uses IDiagnostics.
+static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
+ diag->Warn(DiagMessage() << warning_msg);
+}
+
+// Custom error logging method that uses IDiagnostics.
+static void LogError(png_structp png_ptr, png_const_charp error_msg) {
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
+ diag->Error(DiagMessage() << error_msg);
+}
+
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
+ png_size_t len) {
+ io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
+
+ const void* in_buffer;
+ int in_len;
+ if (!in->Next(&in_buffer, &in_len)) {
+ if (in->HadError()) {
+ std::string err = in->GetError();
+ png_error(png_ptr, err.c_str());
+ }
+ return;
+ }
+
+ const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
+ memcpy(buffer, in_buffer, bytes_read);
+ if (bytes_read != static_cast<size_t>(in_len)) {
+ in->BackUp(in_len - static_cast<int>(bytes_read));
+ }
+}
+
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
+ png_size_t len) {
+ io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
+
+ void* out_buffer;
+ int out_len;
+ while (len > 0) {
+ if (!out->Next(&out_buffer, &out_len)) {
+ if (out->HadError()) {
+ std::string err = out->GetError();
+ png_error(png_ptr, err.c_str());
+ }
+ return;
+ }
+
+ const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
+ memcpy(out_buffer, buffer, bytes_written);
+
+ // Advance the input buffer.
+ buffer += bytes_written;
+ len -= bytes_written;
+
+ // Advance the output buffer.
+ out_len -= static_cast<int>(bytes_written);
+ }
+
+ // If the entire output buffer wasn't used, backup.
+ if (out_len > 0) {
+ out->BackUp(out_len);
+ }
+}
+
+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 buffer_size;
+ if (!in->Next((const void**)&signature, &buffer_size)) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << android::base::SystemErrorCodeToString(errno));
+ return {};
+ }
+
+ if (static_cast<size_t>(buffer_size) < 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(buffer_size - 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 read_ptr =
+ png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (read_ptr == 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 info_ptr = png_create_info_struct(read_ptr);
+ if (info_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng read png_info");
+ png_destroy_read_struct(&read_ptr, nullptr, nullptr);
+ return {};
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
+
+ // 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(read_ptr))) {
+ return {};
+ }
+
+ // Handle warnings ourselves via IDiagnostics.
+ png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
+ LogWarning);
+
+ // Set up the read functions which read from our custom data sources.
+ png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
+
+ // Skip the signature that we already read.
+ png_set_sig_bytes(read_ptr, kPngSignatureSize);
+
+ // Read the chunk headers.
+ png_read_info(read_ptr, info_ptr);
+
+ // Extract image meta-data from the various chunk headers.
+ uint32_t width, height;
+ int bit_depth, color_type, interlace_method, compression_method,
+ filter_method;
+ png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_method, &compression_method, &filter_method);
+
+ // When the image is read, expand it so that it is in RGBA 8888 format
+ // so that image handling is uniform.
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(read_ptr);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
+ png_set_expand_gray_1_2_4_to_8(read_ptr);
+ }
+
+ if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(read_ptr);
+ }
+
+ if (bit_depth == 16) {
+ png_set_strip_16(read_ptr);
+ }
+
+ if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(read_ptr);
+ }
+
+ if (interlace_method != PNG_INTERLACE_NONE) {
+ png_set_interlace_handling(read_ptr);
+ }
+
+ // Once all the options for reading have been set, we need to flush
+ // them to libpng.
+ png_read_update_info(read_ptr, info_ptr);
+
+ // 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> output_image = util::make_unique<Image>();
+ output_image->width = static_cast<int32_t>(width);
+ output_image->height = static_cast<int32_t>(height);
+
+ const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
+ CHECK(row_bytes == 4 * width); // RGBA
+
+ // Allocate one large block to hold the image.
+ output_image->data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+
+ // Create an array of rows that index into the data block.
+ output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
+ for (uint32_t h = 0; h < height; h++) {
+ output_image->rows[h] = output_image->data.get() + (h * row_bytes);
+ }
+
+ // Actually read the image pixels.
+ png_read_image(read_ptr, output_image->rows.get());
+
+ // Finish reading. This will read any other chunks after the image data.
+ png_read_end(read_ptr, info_ptr);
+
+ return output_image;
+}
+
+/**
+ * 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
+// the least amount of disk space.
+//
+// 9-patch images traditionally have not been encoded with palettes.
+// The original rationale was to avoid dithering until after scaling,
+// but I don't think this would be an issue with palettes. Either way,
+// our naive size estimation tends to be wrong for small images like 9-patches
+// and using palettes balloons the size of the resulting 9-patch.
+// In order to not regress in size, restrict 9-patch to not use palettes.
+
+// The options are:
+//
+// - RGB
+// - RGBA
+// - RGB + cheap alpha
+// - Color palette
+// - Color palette + cheap alpha
+// - Color palette + alpha palette
+// - Grayscale
+// - Grayscale + cheap alpha
+// - Grayscale + alpha
+//
+static int PickColorType(int32_t width, int32_t height, bool grayscale,
+ bool convertible_to_grayscale, bool has_nine_patch,
+ size_t color_palette_size, size_t alpha_palette_size) {
+ const size_t palette_chunk_size = 16 + color_palette_size * 3;
+ const size_t alpha_chunk_size = 16 + alpha_palette_size;
+ const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
+ const size_t color_data_chunk_size = 16 + 3 * width * height;
+ const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
+ const size_t palette_data_chunk_size = 16 + width * height;
+
+ if (grayscale) {
+ if (alpha_palette_size == 0) {
+ // This is the smallest the data can be.
+ return PNG_COLOR_TYPE_GRAY;
+ } else if (color_palette_size <= 256 && !has_nine_patch) {
+ // This grayscale has alpha and can fit within a palette.
+ // See if it is worth fitting into a palette.
+ const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
+ palette_data_chunk_size +
+ kPaletteOverheadConstant;
+ if (grayscale_alpha_data_chunk_size > palette_threshold) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ if (color_palette_size <= 256 && !has_nine_patch) {
+ // This image can fit inside a palette. Let's see if it is worth it.
+ size_t total_size_with_palette =
+ palette_data_chunk_size + palette_chunk_size;
+ size_t total_size_without_palette = color_data_chunk_size;
+ if (alpha_palette_size > 0) {
+ total_size_with_palette += alpha_palette_size;
+ total_size_without_palette = color_alpha_data_chunk_size;
+ }
+
+ if (total_size_without_palette >
+ total_size_with_palette + kPaletteOverheadConstant) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+
+ if (convertible_to_grayscale) {
+ if (alpha_palette_size == 0) {
+ return PNG_COLOR_TYPE_GRAY;
+ } else {
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+ }
+
+ if (alpha_palette_size == 0) {
+ return PNG_COLOR_TYPE_RGB;
+ }
+ return PNG_COLOR_TYPE_RGBA;
+}
+
+// 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.
+static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
+ std::unordered_map<uint32_t, int>* color_palette,
+ std::unordered_set<uint32_t>* alpha_palette) {
+ CHECK(color_palette->size() <= 256);
+ CHECK(alpha_palette->size() <= 256);
+
+ // 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 : *alpha_palette) {
+ (*color_palette)[color] = index++;
+ }
+
+ // Assign the rest of the entries.
+ for (auto& entry : *color_palette) {
+ if (entry.second == -1) {
+ entry.second = index++;
+ }
+ }
+
+ // Create the PNG color palette struct.
+ auto color_palette_bytes =
+ std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+
+ std::unique_ptr<png_byte[]> alpha_palette_bytes;
+ if (!alpha_palette->empty()) {
+ alpha_palette_bytes =
+ std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+ }
+
+ for (const auto& entry : *color_palette) {
+ const uint32_t color = entry.first;
+ const int index = entry.second;
+ CHECK(index >= 0);
+ CHECK(static_cast<size_t>(index) < color_palette->size());
+
+ png_colorp slot = color_palette_bytes.get() + index;
+ slot->red = color >> 24;
+ slot->green = color >> 16;
+ slot->blue = color >> 8;
+
+ const png_byte alpha = color & 0x000000ff;
+ if (alpha != 0xff && alpha_palette_bytes) {
+ CHECK(static_cast<size_t>(index) < alpha_palette->size());
+ alpha_palette_bytes[index] = alpha;
+ }
+ }
+
+ // The bytes get copied here, so it is safe to release color_palette_bytes at
+ // the end of function
+ // scope.
+ png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
+ color_palette->size());
+
+ if (alpha_palette_bytes) {
+ png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
+ alpha_palette->size(), nullptr);
+ }
+}
+
+// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
+// before
+// writing image data.
+static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
+ const NinePatch* nine_patch) {
+ // The order of the chunks is important.
+ // 9-patch code in older platforms expects the 9-patch chunk to
+ // be last.
+
+ png_unknown_chunk unknown_chunks[3];
+ memset(unknown_chunks, 0, sizeof(unknown_chunks));
+
+ size_t index = 0;
+ size_t chunk_len = 0;
+
+ std::unique_ptr<uint8_t[]> serialized_outline =
+ nine_patch->SerializeRoundedRectOutline(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npOl");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_outline.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ std::unique_ptr<uint8_t[]> serialized_layout_bounds;
+ if (nine_patch->layout_bounds.nonZero()) {
+ serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npLb");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+ }
+
+ std::unique_ptr<uint8_t[]> serialized_nine_patch =
+ nine_patch->SerializeBase(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npTc");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
+ unknown_chunks[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(write_ptr, 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(write_ptr, write_info_ptr, unknown_chunks, index);
+}
+
+bool WritePng(IAaptContext* context, const Image* image,
+ const NinePatch* nine_patch, 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 write_ptr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (write_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng write png_struct");
+ return false;
+ }
+
+ // Allocate memory to store image header data.
+ png_infop write_info_ptr = png_create_info_struct(write_ptr);
+ if (write_info_ptr == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create libpng write png_info");
+ png_destroy_write_struct(&write_ptr, nullptr);
+ return false;
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
+
+ // 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(write_ptr))) {
+ return false;
+ }
+
+ // Handle warnings with our IDiagnostics.
+ png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
+ LogWarning);
+
+ // Set up the write functions which write to our custom data sources.
+ png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
+
+ // We want small files and can take the performance hit to achieve this goal.
+ png_set_compression_level(write_ptr, 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> color_palette;
+ std::unordered_set<uint32_t> alpha_palette;
+ bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
+ bool grayscale = true;
+ int max_gray_deviation = 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.
+ needs_to_zero_rgb_channels_of_transparent_pixels =
+ needs_to_zero_rgb_channels_of_transparent_pixels ||
+ (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;
+ color_palette[color] = -1;
+
+ // If the pixel has non-opaque alpha, insert it into the
+ // alpha palette.
+ if (alpha != 0xff) {
+ alpha_palette.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.
+ max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
+ max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
+ max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
+ }
+ }
+
+ if (context->IsVerbose()) {
+ DiagMessage msg;
+ msg << " paletteSize=" << color_palette.size()
+ << " alphaPaletteSize=" << alpha_palette.size()
+ << " maxGrayDeviation=" << max_gray_deviation
+ << " grayScale=" << (grayscale ? "true" : "false");
+ context->GetDiagnostics()->Note(msg);
+ }
+
+ const bool convertible_to_grayscale =
+ max_gray_deviation <= options.grayscale_tolerance;
+
+ const int new_color_type = PickColorType(
+ image->width, image->height, grayscale, convertible_to_grayscale,
+ nine_patch != nullptr, color_palette.size(), alpha_palette.size());
+
+ if (context->IsVerbose()) {
+ DiagMessage msg;
+ msg << "encoding PNG ";
+ if (nine_patch) {
+ msg << "(with 9-patch) as ";
+ }
+ switch (new_color_type) {
+ 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 " << new_color_type;
+ break;
+ }
+ context->GetDiagnostics()->Note(msg);
+ }
+
+ png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8,
+ new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ if (new_color_type & PNG_COLOR_MASK_PALETTE) {
+ // Assigns indices to the palette, and writes the encoded palette to the
+ // libpng writePtr.
+ WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
+ png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (nine_patch) {
+ WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
+ }
+
+ // Flush our updates to the header.
+ png_write_info(write_ptr, write_info_ptr);
+
+ // Write out each row of image data according to its encoding.
+ if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
+ // 1 byte/pixel.
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *in_row++;
+ int gg = *in_row++;
+ int bb = *in_row++;
+ int aa = *in_row++;
+ 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 = color_palette[color];
+ CHECK(idx != -1);
+ out_row[x] = static_cast<png_byte>(idx);
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else if (new_color_type == PNG_COLOR_TYPE_GRAY ||
+ new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
+ auto out_row =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = in_row[x * 4];
+ int gg = in_row[x * 4 + 1];
+ int bb = in_row[x * 4 + 2];
+ int aa = in_row[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.
+ out_row[x * bpp] = in_row[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
+ out_row[x * bpp] =
+ (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+
+ if (bpp == 2) {
+ // Write out alpha if we have it.
+ out_row[x * bpp + 1] = aa;
+ }
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else if (new_color_type == PNG_COLOR_TYPE_RGB ||
+ new_color_type == PNG_COLOR_TYPE_RGBA) {
+ const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
+ if (needs_to_zero_rgb_channels_of_transparent_pixels) {
+ // The source RGBA data can't be used as-is, because we need to zero out
+ // the RGB
+ // values of transparent pixels.
+ auto out_row =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *in_row++;
+ int gg = *in_row++;
+ int bb = *in_row++;
+ int aa = *in_row++;
+ if (aa == 0) {
+ // Zero out the RGB channels when transparent.
+ rr = gg = bb = 0;
+ }
+ out_row[x * bpp] = rr;
+ out_row[x * bpp + 1] = gg;
+ out_row[x * bpp + 2] = bb;
+ if (bpp == 4) {
+ out_row[x * bpp + 3] = aa;
+ }
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else {
+ // The source image can be used as-is, just tell libpng whether or not to
+ // ignore
+ // the alpha channel.
+ if (new_color_type == PNG_COLOR_TYPE_RGB) {
+ // Delete the extraneous alpha values that we appended to our buffer
+ // when reading the original values.
+ png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+ }
+ png_write_image(write_ptr, image->rows.get());
+ }
+ } else {
+ LOG(FATAL) << "unreachable";
+ }
+
+ png_write_end(write_ptr, write_info_ptr);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 732101f..055a725 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -14,235 +14,249 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "ValueVisitor.h"
#include "compile/PseudolocaleGenerator.h"
-#include "compile/Pseudolocalizer.h"
#include <algorithm>
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "compile/Pseudolocalizer.h"
+
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 original_text = *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.first_char, span.last_char});
+ }
+
+ // 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* update_start;
+ uint32_t* update_end;
+ };
+
+ 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{original_text.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.first_char, cmp);
+ if (iter != ranges.end() && iter->start == span.first_char) {
+ iter->update_start = &localized.spans[i].first_char;
+ } else {
+ ranges.insert(iter, Range{span.first_char, &localized.spans[i].first_char,
+ 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.last_char, cmp);
+ if (iter != ranges.end() && iter->start == span.last_char) {
+ iter->update_end = &localized.spans[i].last_char;
+ } else {
+ ranges.insert(
+ iter, Range{span.last_char, nullptr, &localized.spans[i].last_char});
+ }
+ }
- // 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 = original_text.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].update_start) {
+ *ranges[i].update_start = localized.str.size();
}
- localized.str += localizer.end();
+ if (ranges[i].update_end) {
+ *ranges[i].update_end = 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(original_text.substr(start, len));
+ }
+
+ localized.str += localizer.End();
+
+ std::unique_ptr<StyledString> localized_string =
+ util::make_unique<StyledString>(pool->MakeRef(localized));
+ localized_string->SetSource(string->GetSource());
+ return localized_string;
}
namespace {
-struct Visitor : public RawValueVisitor {
- StringPool* mPool;
- Pseudolocalizer::Method mMethod;
- Pseudolocalizer mLocalizer;
+class Visitor : public RawValueVisitor {
+ public:
+ // Either value or item will be populated upon visiting the value.
+ std::unique_ptr<Value> value;
+ std::unique_ptr<Item> item;
- // 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)
+ : pool_(pool), method_(method), localizer_(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 sub_visitor(pool_, method_);
+ if (plural->values[i]) {
+ plural->values[i]->Accept(&sub_visitor);
+ if (sub_visitor.value) {
+ localized->values[i] = std::move(sub_visitor.item);
+ } else {
+ localized->values[i] =
+ std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
}
- localized->setSource(plural->getSource());
- localized->setWeak(true);
- mValue = std::move(localized);
+ }
}
+ localized->SetSource(plural->GetSource());
+ localized->SetWeak(true);
+ value = 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 =
+ localizer_.Start() + localizer_.Text(*string->value) + localizer_.End();
+ std::unique_ptr<String> localized =
+ util::make_unique<String>(pool_->MakeRef(result));
+ localized->SetSource(string->GetSource());
+ localized->SetWeak(true);
+ item = std::move(localized);
+ }
- void visit(StyledString* string) override {
- mItem = pseudolocalizeStyledString(string, mMethod, mPool);
- mItem->setWeak(true);
- }
+ void Visit(StyledString* string) override {
+ item = PseudolocalizeStyledString(string, method_, pool_);
+ item->SetWeak(true);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+ StringPool* pool_;
+ Pseudolocalizer::Method method_;
+ Pseudolocalizer localizer_;
};
-ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
+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);
+void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
+ ResourceConfigValue* original_value,
+ StringPool* pool, ResourceEntry* entry) {
+ Visitor visitor(pool, method);
+ original_value->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> localized_value;
+ if (visitor.value) {
+ localized_value = std::move(visitor.value);
+ } else if (visitor.item) {
+ localized_value = std::move(visitor.item);
+ }
- if (!localizedValue) {
- return;
- }
+ if (!localized_value) {
+ return;
+ }
- ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
- originalValue->config, method);
+ ConfigDescription config_with_accent =
+ ModifyConfigForPseudoLocale(original_value->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* new_config_value =
+ entry->FindOrCreateValue(config_with_accent, original_value->product);
+ if (!new_config_value->value) {
+ // Only use auto-generated pseudo-localization if none is defined.
+ new_config_value->value = std::move(localized_value);
+ }
}
/**
- * 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();
+static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
+ const int diff =
+ config_value->config.diff(ConfigDescription::DefaultConfig());
+ if (diff & ConfigDescription::CONFIG_LOCALE) {
+ return false;
+ }
+ return config_value->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->string_pool, entry.get());
+ PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
+ &table->string_pool, 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..ace3786 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..5a9884d 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -15,107 +15,119 @@
*/
#include "compile/PseudolocaleGenerator.h"
+
#include "test/Test.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-
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 original_style;
+ original_style.str = "Hello world!";
+ original_style.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> new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
+ Pseudolocalizer::Method::kNone, &pool);
- EXPECT_EQ(originalStyle.str, *newString->value->str);
- ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+ EXPECT_EQ(original_style.str, *new_string->value->str);
+ ASSERT_EQ(original_style.spans.size(), new_string->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(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char);
+ EXPECT_EQ(std::string("b"), *new_string->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(),
+ new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::string("Hello w").size(),
+ new_string->value->spans[1].last_char);
+ EXPECT_EQ(std::string("b"), *new_string->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(), new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::string("Hello worl").size(),
+ new_string->value->spans[2].last_char);
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name);
- originalStyle.spans.push_back(Span{ "em", 0, 11u });
+ original_style.spans.push_back(Span{"em", 0, 11u});
- newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kAccent, &pool);
+ new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).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]"), *new_string->value->str);
+ ASSERT_EQ(original_style.spans.size(), new_string->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(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char);
- 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(),
+ new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
+ new_string->value->spans[1].last_char);
- 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(), new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
+ new_string->value->spans[2].last_char);
- 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(), new_string->value->spans[3].first_char);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
+ new_string->value->spans[3].last_char);
}
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..f89288f 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -15,251 +15,333 @@
*/
#include "compile/Pseudolocalizer.h"
+
#include "util/Util.h"
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 kExpansionString =
+ "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";
-static const std::string k_rlo = "\u202e";
-static const std::string k_pdf = "\u202c";
+static const std::string kRlm = "\u200f";
+static const std::string kRlo = "\u202e";
+static const std::string kPdf = "\u202c";
// Placeholder marks
-static const std::string k_placeholder_open = "\u00bb";
-static const std::string k_placeholder_close = "\u00ab";
+static const std::string kPlaceholderOpen = "\u00bb";
+static const std::string kPlaceholderClose = "\u00ab";
-static const char k_arg_start = '{';
-static const char k_arg_end = '}';
+static const char kArgStart = '{';
+static const char kArgEnd = '}';
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() : depth_(0), word_count_(0), length_(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 depth_;
+ size_t word_count_;
+ size_t length_;
};
-Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
- setMethod(method);
+Pseudolocalizer::Pseudolocalizer(Method method) : last_depth_(0) {
+ SetMethod(method);
}
-void Pseudolocalizer::setMethod(Method method) {
- switch (method) {
+void Pseudolocalizer::SetMethod(Method method) {
+ switch (method) {
case Method::kNone:
- mImpl = util::make_unique<PseudoMethodNone>();
- break;
+ impl_ = util::make_unique<PseudoMethodNone>();
+ break;
case Method::kAccent:
- mImpl = util::make_unique<PseudoMethodAccent>();
- break;
+ impl_ = util::make_unique<PseudoMethodAccent>();
+ break;
case Method::kBidi:
- mImpl = util::make_unique<PseudoMethodBidi>();
+ impl_ = util::make_unique<PseudoMethodBidi>();
+ break;
+ }
+}
+
+std::string Pseudolocalizer::Text(const StringPiece& text) {
+ std::string out;
+ size_t depth = last_depth_;
+ 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 == kArgStart) {
+ depth++;
+ } else if (c == kArgEnd && depth) {
+ depth--;
+ }
+
+ if (last_depth_ != depth || pos == length - 1) {
+ bool pseudo = ((last_depth_ % 2) == 0);
+ size_t nextpos = pos;
+ if (!pseudo || depth == last_depth_) {
+ nextpos++;
+ }
+ size_t size = nextpos - lastpos;
+ if (size) {
+ std::string chunk = text.substr(lastpos, size).ToString();
+ if (pseudo) {
+ chunk = impl_->Text(chunk);
+ } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
+ chunk = impl_->Placeholder(chunk);
+ }
+ out.append(chunk);
+ }
+ if (pseudo && depth < last_depth_) { // End of message
+ out.append(impl_->End());
+ } else if (!pseudo && depth > last_depth_) { // Start of message
+ out.append(impl_->Start());
+ }
+ lastpos = nextpos;
+ last_depth_ = 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;
+ }
+}
+
+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;
+ }
+}
+
+static std::string PseudoGenerateExpansion(const unsigned int length) {
+ std::string result = kExpansionString;
+ 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);
+ }
+ return result;
}
-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;
- }
- }
- return out;
+std::string PseudoMethodAccent::Start() {
+ std::string result;
+ if (depth_ == 0) {
+ result = "[";
+ }
+ word_count_ = length_ = 0;
+ depth_++;
+ return result;
}
-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;
- }
-}
-
-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;
- }
-}
-
-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);
- }
- return result;
-}
-
-std::string PseudoMethodAccent::start() {
- 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 PseudoMethodAccent::End() {
+ std::string result;
+ if (length_) {
+ result += " ";
+ result += PseudoGenerateExpansion(word_count_ > 3 ? length_ : length_ / 2);
+ }
+ word_count_ = length_ = 0;
+ depth_--;
+ if (depth_ == 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 escape_text;
+ escape_text.append(&c, 1);
+ bool end = false;
+ size_t html_code_pos = i;
+ while (!end && html_code_pos < I) {
+ ++html_code_pos;
+ c = s[html_code_pos];
+ escape_text.append(&c, 1);
+ // Valid html code
+ if (c == ';') {
+ end = true;
+ i = html_code_pos;
+ }
+ // Wrong html code
+ else if (!((c == '#' || (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) {
+ end = true;
+ }
+ }
+ result += escape_text;
+ if (escape_text != "<") {
+ 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;
+ word_count_++;
}
lastspace = space;
result.append(&c, 1);
+ }
+ // Count only pseudolocalizable chars and delimiters
+ length_++;
}
- if (!lastspace) {
- // End of last word
- result += k_pdf + k_rlm;
- }
- return result;
+ }
+ 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;
+std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
+ // Surround a placeholder with brackets
+ return kPlaceholderOpen + source.ToString() + kPlaceholderClose;
}
-} // namespace aapt
+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 += kRlm + kRlo;
+ } else if (!lastspace && space) {
+ // Word end
+ result += kPdf + kRlm;
+ }
+ lastspace = space;
+ result.append(&c, 1);
+ }
+ if (!lastspace) {
+ // End of last word
+ result += kPdf + kRlm;
+ }
+ return result;
+}
+
+std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
+ // Surround a placeholder with directionality change sequence
+ return kRlm + kRlo + source.ToString() + kPdf + kRlm;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 91d17d174..a6d2ad0 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -17,42 +17,44 @@
#ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H
#define AAPT_COMPILE_PSEUDOLOCALIZE_H
+#include <memory>
+
+#include "android-base/macros.h"
+
#include "ResourceValues.h"
#include "StringPool.h"
#include "util/StringPiece.h"
-#include <android-base/macros.h>
-#include <memory>
-
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 impl_->Start(); }
+ std::string End() { return impl_->End(); }
+ std::string Text(const StringPiece& text);
+
+ private:
+ std::unique_ptr<PseudoMethodImpl> impl_;
+ size_t last_depth_;
};
-} // 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..92eb3b5 100644
--- a/tools/aapt2/compile/Pseudolocalizer_test.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -15,209 +15,216 @@
*/
#include "compile/Pseudolocalizer.h"
-#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <gtest/gtest.h>
+#include "test/Test.h"
+#include "util/Util.h"
namespace aapt {
// 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..d61a15a 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -14,57 +14,61 @@
* limitations under the License.
*/
-#include "ResourceUtils.h"
-#include "ResourceValues.h"
#include "compile/XmlIdCollector.h"
-#include "xml/XmlDom.h"
#include <algorithm>
#include <vector>
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
namespace {
-static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
- return a.name < b;
+static bool cmp_name(const SourcedResourceName& a, const ResourceNameRef& b) {
+ return a.name < b;
}
struct IdCollector : public xml::Visitor {
- using xml::Visitor::visit;
+ public:
+ using xml::Visitor::Visit;
- std::vector<SourcedResourceName>* mOutSymbols;
+ explicit IdCollector(std::vector<SourcedResourceName>* out_symbols)
+ : out_symbols_(out_symbols) {}
- 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(out_symbols_->begin(),
+ out_symbols_->end(), name, cmp_name);
+ if (iter == out_symbols_->end() || iter->name != name) {
+ out_symbols_->insert(iter,
+ SourcedResourceName{name.ToResourceName(),
+ element->line_number});
+ }
}
-
- xml::Visitor::visit(element);
+ }
}
+
+ xml::Visitor::Visit(element);
+ }
+
+ private:
+ std::vector<SourcedResourceName>* out_symbols_;
};
-} // 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;
+bool XmlIdCollector::Consume(IAaptContext* context, xml::XmlResource* xmlRes) {
+ xmlRes->file.exported_symbols.clear();
+ IdCollector collector(&xmlRes->file.exported_symbols);
+ 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..8febf0f 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* xml_res) 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..98da56d 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -15,18 +15,17 @@
*/
#include "compile/XmlIdCollector.h"
-#include "test/Builders.h"
-#include "test/Context.h"
#include <algorithm>
-#include <gtest/gtest.h>
+
+#include "test/Test.h"
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 +33,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.exported_symbols.begin(),
+ doc->file.exported_symbols.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.exported_symbols.begin(),
+ doc->file.exported_symbols.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.exported_symbols.begin(),
+ doc->file.exported_symbols.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.exported_symbols.empty());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
index 9b1f057..593e7ab 100644
--- a/tools/aapt2/diff/Diff.cpp
+++ b/tools/aapt2/diff/Diff.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "android-base/macros.h"
+
#include "Flags.h"
#include "ResourceTable.h"
#include "ValueVisitor.h"
@@ -22,417 +24,409 @@
#include "process/SymbolTable.h"
#include "unflatten/BinaryResourceParser.h"
-#include <android-base/macros.h>
-
namespace aapt {
class DiffContext : public IAaptContext {
-public:
- const std::string& getCompilationPackage() override {
- return mEmpty;
- }
+ public:
+ const std::string& GetCompilationPackage() override { return empty_; }
- uint8_t getPackageId() override {
- return 0x0;
- }
+ uint8_t GetPackageId() override { return 0x0; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* GetNameMangler() override { return &name_mangler_; }
- SymbolTable* getExternalSymbols() override {
- return &mSymbolTable;
- }
+ SymbolTable* GetExternalSymbols() override { return &symbol_table_; }
- bool verbose() override {
- return false;
- }
+ bool IsVerbose() 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 empty_;
+ StdErrDiagnostics diagnostics_;
+ NameMangler name_mangler_ = NameMangler(NameManglerPolicy{});
+ SymbolTable symbol_table_;
};
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)
+ : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
- io::IFileCollection* getFileCollection() {
- return mApk.get();
- }
+ io::IFileCollection* GetFileCollection() { return apk_.get(); }
- ResourceTable* getResourceTable() {
- return mTable.get();
- }
+ ResourceTable* GetResourceTable() { return table_.get(); }
- const Source& getSource() {
- return mSource;
- }
+ const Source& GetSource() { return source_; }
-private:
- Source mSource;
- std::unique_ptr<io::IFileCollection> mApk;
- std::unique_ptr<ResourceTable> mTable;
+ private:
+ Source source_;
+ std::unique_ptr<io::IFileCollection> apk_;
+ std::unique_ptr<ResourceTable> table_;
- 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";
+static void EmitDiffLine(const Source& source, const StringPiece& message) {
+ std::cerr << source << ": " << message << "\n";
}
-static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
- return symbolA.state != symbolB.state;
+static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a,
+ const Symbol& symbol_b) {
+ return symbol_a.state != symbol_b.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;
+static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a,
+ const Symbol& symbol_b, const Maybe<Id>& id_b) {
+ if (symbol_a.state == SymbolState::kPublic ||
+ symbol_b.state == SymbolState::kPublic) {
+ return id_a != id_b;
+ }
+ 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* apk_a, ResourceTablePackage* pkg_a,
+ ResourceTableType* type_a, ResourceEntry* entry_a,
+ ResourceConfigValue* config_value_a, LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b, ResourceTableType* type_b,
+ ResourceEntry* entry_b, ResourceConfigValue* config_value_b) {
+ Value* value_a = config_value_a->value.get();
+ Value* value_b = config_value_b->value.get();
+ if (!value_a->Equals(value_b)) {
+ std::stringstream str_stream;
+ str_stream << "value " << pkg_a->name << ":" << type_a->type << "/"
+ << entry_a->name << " config=" << config_value_a->config
+ << " does not match:\n";
+ value_a->Print(&str_stream);
+ str_stream << "\n vs \n";
+ value_b->Print(&str_stream);
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ return true;
+ }
+ return false;
}
-static bool emitResourceEntryDiff(IAaptContext* context,
- LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- ResourceTableType* typeA,
- 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;
+static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
+ ResourceTablePackage* pkg_a,
+ ResourceTableType* type_a,
+ ResourceEntry* entry_a, LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b,
+ ResourceTableType* type_b,
+ ResourceEntry* entry_b) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
+ ResourceConfigValue* config_value_b =
+ entry_b->FindValue(config_value_a->config);
+ if (!config_value_b) {
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
+ << entry_a->name << " config=" << config_value_a->config;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ diff |= EmitResourceConfigValueDiff(
+ context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), apk_b,
+ pkg_b, type_b, entry_b, config_value_b);
+ }
+ }
+
+ // Check for any newly added config values.
+ for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
+ ResourceConfigValue* config_value_a =
+ entry_a->FindValue(config_value_b->config);
+ if (!config_value_a) {
+ std::stringstream str_stream;
+ str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/"
+ << entry_b->name << " config=" << config_value_b->config;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ }
+ return false;
+}
+
+static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
+ ResourceTablePackage* pkg_a,
+ ResourceTableType* type_a, LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b,
+ ResourceTableType* type_b) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) {
+ ResourceEntry* entry_b = type_b->FindEntry(entry_a->name);
+ if (!entry_b) {
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
+ << entry_a->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ if (IsSymbolVisibilityDifferent(entry_a->symbol_status,
+ entry_b->symbol_status)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ << " has different visibility (";
+ if (entry_b->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
} else {
- diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
- configValueA.get(), apkB, pkgB, typeB, entryB,
- configValueB);
+ str_stream << "PRIVATE";
}
+ str_stream << " vs ";
+ if (entry_a->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
+ } else {
+ str_stream << "PRIVATE";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else if (IsIdDiff(entry_a->symbol_status, entry_a->id,
+ entry_b->symbol_status, entry_b->id)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ << " has different public ID (";
+ if (entry_b->id) {
+ str_stream << "0x" << std::hex << entry_b->id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << " vs ";
+ if (entry_a->id) {
+ str_stream << "0x " << std::hex << entry_a->id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ diff |=
+ EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(),
+ apk_b, pkg_b, type_b, entry_b);
}
+ }
- // 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 entries.
+ for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) {
+ ResourceEntry* entry_a = type_a->FindEntry(entry_b->name);
+ if (!entry_a) {
+ std::stringstream str_stream;
+ str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/"
+ << entry_b->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
}
- return false;
+ }
+ return diff;
}
-static bool emitResourceTypeDiff(IAaptContext* context,
- LoadedApk* apkA,
- ResourceTablePackage* pkgA,
- 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;
+static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
+ ResourceTablePackage* pkg_a,
+ LoadedApk* apk_b,
+ ResourceTablePackage* pkg_b) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) {
+ ResourceTableType* type_b = pkg_b->FindType(type_a->type);
+ if (!type_b) {
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a->name << ":" << type_a->type;
+ EmitDiffLine(apk_a->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ if (IsSymbolVisibilityDifferent(type_a->symbol_status,
+ type_b->symbol_status)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type
+ << " has different visibility (";
+ if (type_b->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "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);
+ str_stream << "PRIVATE";
}
+ str_stream << " vs ";
+ if (type_a->symbol_status.state == SymbolState::kPublic) {
+ str_stream << "PUBLIC";
+ } else {
+ str_stream << "PRIVATE";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else if (IsIdDiff(type_a->symbol_status, type_a->id,
+ type_b->symbol_status, type_b->id)) {
+ std::stringstream str_stream;
+ str_stream << pkg_a->name << ":" << type_a->type
+ << " has different public ID (";
+ if (type_b->id) {
+ str_stream << "0x" << std::hex << type_b->id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << " vs ";
+ if (type_a->id) {
+ str_stream << "0x " << std::hex << type_a->id.value();
+ } else {
+ str_stream << "none";
+ }
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b,
+ pkg_b, type_b);
}
+ }
- // 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 types.
+ for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
+ ResourceTableType* type_a = pkg_a->FindType(type_b->type);
+ if (!type_a) {
+ std::stringstream str_stream;
+ str_stream << "new type " << pkg_b->name << ":" << type_b->type;
+ EmitDiffLine(apk_b->GetSource(), str_stream.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;
+static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a,
+ LoadedApk* apk_b) {
+ ResourceTable* table_a = apk_a->GetResourceTable();
+ ResourceTable* table_b = apk_b->GetResourceTable();
+
+ bool diff = false;
+ for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
+ ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
+ if (!pkg_b) {
+ std::stringstream str_stream;
+ str_stream << "missing package " << pkg_a->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ if (pkg_a->id != pkg_b->id) {
+ std::stringstream str_stream;
+ str_stream << "package '" << pkg_a->name << "' has different id (";
+ if (pkg_b->id) {
+ str_stream << "0x" << std::hex << pkg_b->id.value();
} 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);
+ str_stream << "none";
}
- }
-
- // 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;
-}
-
-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;
+ str_stream << " vs ";
+ if (pkg_a->id) {
+ str_stream << "0x" << std::hex << pkg_a->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);
+ str_stream << "none";
}
+ str_stream << ")";
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ diff |=
+ EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
}
+ }
- // 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>& pkg_b : table_b->packages) {
+ ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
+ if (!pkg_a) {
+ std::stringstream str_stream;
+ str_stream << "new package " << pkg_b->name;
+ EmitDiffLine(apk_b->GetSource(), str_stream.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().package_id() == 0x7f) {
+ ref->id = {};
+ }
}
+ }
};
-static void zeroOutAppReferences(ResourceTable* table) {
- ZeroingReferenceVisitor visitor;
- visitAllValuesInTable(table, &visitor);
+static void ZeroOutAppReferences(ResourceTable* table) {
+ ZeroingReferenceVisitor visitor;
+ VisitAllValuesInTable(table, &visitor);
}
-int diff(const std::vector<StringPiece>& args) {
- DiffContext context;
+int Diff(const std::vector<StringPiece>& args) {
+ 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> apk_a =
+ LoadApkFromPath(&context, flags.GetArgs()[0]);
+ std::unique_ptr<LoadedApk> apk_b =
+ LoadApkFromPath(&context, flags.GetArgs()[1]);
+ if (!apk_a || !apk_b) {
+ return 1;
+ }
- // Zero out Application IDs in references.
- zeroOutAppReferences(apkA->getResourceTable());
- zeroOutAppReferences(apkB->getResourceTable());
+ // Zero out Application IDs in references.
+ ZeroOutAppReferences(apk_a->GetResourceTable());
+ ZeroOutAppReferences(apk_b->GetResourceTable());
- if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
- // We emitted a diff, so return 1 (failure).
- return 1;
- }
- return 0;
+ if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.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..2920c2a 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <vector>
+
#include "Debug.h"
#include "Diagnostics.h"
#include "Flags.h"
@@ -24,187 +26,179 @@
#include "util/Files.h"
#include "util/StringPiece.h"
-#include <vector>
-
namespace aapt {
-//struct DumpOptions {
-//
-//};
+void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data,
+ size_t len, const Source& source, IAaptContext* context) {
+ std::unique_ptr<ResourceFile> file =
+ DeserializeCompiledFileFromPb(pb_file, 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;
+void TryDumpFile(IAaptContext* context, const std::string& file_path) {
+ 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(file_path, &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(file_path) << "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 pb_table;
+ if (!pb_table.ParseFromArray(data->data(), data->size())) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "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(pb_table, Source(file_path),
+ 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(file_path)
+ << "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(file_path),
+ 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(file_path, &err);
+ if (!file) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
+ return;
}
- if (table) {
- DebugPrintTableOptions debugPrintTableOptions;
- debugPrintTableOptions.showSources = true;
- Debug::printTable(table.get(), debugPrintTableOptions);
+ android::FileMap* file_map = &file.value();
+
+ // Try as a compiled table.
+ pb::ResourceTable pb_table;
+ if (pb_table.ParseFromArray(file_map->getDataPtr(),
+ file_map->getDataLength())) {
+ table = DeserializeTableFromPb(pb_table, Source(file_path),
+ context->GetDiagnostics());
}
+
+ if (!table) {
+ // Try as a compiled file.
+ CompiledFileInputStream input(file_map->getDataPtr(),
+ file_map->getDataLength());
+
+ uint32_t num_files = 0;
+ if (!input.ReadLittleEndian32(&num_files)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < num_files; i++) {
+ pb::CompiledFile compiled_file;
+ if (!input.ReadCompiledFile(&compiled_file)) {
+ 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*>(file_map->getDataPtr()) + offset;
+ DumpCompiledFile(compiled_file, data, len, Source(file_path), context);
+ }
+ }
+ }
+
+ if (table) {
+ DebugPrintTableOptions options;
+ options.show_sources = true;
+ Debug::PrintTable(table.get(), options);
+ }
}
class DumpContext : public IAaptContext {
-public:
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ public:
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- 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 IsVerbose() override { return verbose_; }
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ void SetVerbose(bool val) { verbose_ = val; }
- int getMinSdkVersion() override {
- return 0;
- }
+ int GetMinSdkVersion() override { return 0; }
-private:
- StdErrDiagnostics mDiagnostics;
- bool mVerbose = false;
+ private:
+ StdErrDiagnostics diagnostics_;
+ bool verbose_ = 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;
- }
+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;
+ }
- 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..66aff82 100644
--- a/tools/aapt2/filter/ConfigFilter.cpp
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -14,64 +14,69 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
#include "filter/ConfigFilter.h"
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/ResourceTypes.h"
+
+#include "ConfigDescription.h"
namespace aapt {
-void AxisConfigFilter::addConfig(ConfigDescription config) {
- uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
+void AxisConfigFilter::AddConfig(ConfigDescription config) {
+ uint32_t diff_mask = ConfigDescription::DefaultConfig().diff(config);
- // Ignore the version
- diffMask &= ~android::ResTable_config::CONFIG_VERSION;
+ // Ignore the version
+ diff_mask &= ~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 ((diff_mask & android::ResTable_config::CONFIG_DENSITY) != 0) {
+ config.density = 0;
+ diff_mask &= ~android::ResTable_config::CONFIG_DENSITY;
+ }
- mConfigs.insert(std::make_pair(config, diffMask));
- mConfigMask |= diffMask;
+ configs_.insert(std::make_pair(config, diff_mask));
+ config_mask_ |= diff_mask;
}
-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;
- }
+bool AxisConfigFilter::Match(const ConfigDescription& config) const {
+ const uint32_t mask = ConfigDescription::DefaultConfig().diff(config);
+ if ((config_mask_ & 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 matched_axis = 0;
+ for (const auto& entry : configs_) {
+ const ConfigDescription& target = entry.first;
+ const uint32_t diff_mask = entry.second;
+ uint32_t diff = target.diff(config);
+ if ((diff & diff_mask) == 0) {
+ // Mark the axis that was matched.
+ matched_axis |= diff_mask;
+ } else if ((diff & diff_mask) == 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) {
+ matched_axis |= android::ResTable_config::CONFIG_LOCALE;
}
+ }
+ } else if ((diff & diff_mask) ==
+ 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) {
+ matched_axis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
+ }
}
- return matchedAxis == (mConfigMask & mask);
+ }
+ return matched_axis == (config_mask_ & mask);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
index 36e9c44..3f13416 100644
--- a/tools/aapt2/filter/ConfigFilter.h
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -17,45 +17,48 @@
#ifndef AAPT_FILTER_CONFIGFILTER_H
#define AAPT_FILTER_CONFIGFILTER_H
-#include "ConfigDescription.h"
-
#include <set>
#include <utility>
+#include "ConfigDescription.h"
+
namespace aapt {
/**
* 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>> configs_;
+ uint32_t config_mask_ = 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..586dd5f 100644
--- a/tools/aapt2/filter/ConfigFilter_test.cpp
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -15,98 +15,98 @@
*/
#include "filter/ConfigFilter.h"
-#include "test/Common.h"
-#include <gtest/gtest.h>
+#include "test/Test.h"
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..47de0a3 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -15,170 +15,186 @@
*/
#include "flatten/Archive.h"
-#include "util/Files.h"
-#include "util/StringPiece.h"
#include <cstdio>
#include <memory>
#include <string>
#include <vector>
-#include <ziparchive/zip_writer.h>
+
+#include "android-base/macros.h"
+#include "ziparchive/zip_writer.h"
+
+#include "util/Files.h"
+#include "util/StringPiece.h"
namespace aapt {
namespace {
-struct DirectoryWriter : public IArchiveWriter {
- std::string mOutDir;
- std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
+class DirectoryWriter : public IArchiveWriter {
+ public:
+ DirectoryWriter() = default;
- 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& out_dir) {
+ dir_ = out_dir.ToString();
+ file::FileType type = file::GetFileType(dir_);
+ if (type == file::FileType::kNonexistant) {
+ diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->Error(DiagMessage() << dir_ << " is not a directory");
+ return false;
+ }
+ return true;
+ }
+
+ bool StartEntry(const StringPiece& path, uint32_t flags) override {
+ if (file_) {
+ return false;
}
- bool startEntry(const StringPiece& path, uint32_t flags) override {
- if (mFile) {
- return false;
- }
+ std::string full_path = dir_;
+ file::AppendPath(&full_path, path);
+ file::mkdirs(file::GetStem(full_path));
- std::string fullPath = mOutDir;
- file::appendPath(&fullPath, path);
- file::mkdirs(file::getStem(fullPath));
+ file_ = {fopen(full_path.data(), "wb"), fclose};
+ if (!file_) {
+ return false;
+ }
+ return true;
+ }
- mFile = { fopen(fullPath.data(), "wb"), fclose };
- if (!mFile) {
- return false;
- }
- return true;
+ bool WriteEntry(const BigBuffer& buffer) override {
+ if (!file_) {
+ 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;
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) {
+ file_.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 WriteEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, file_.get()) != len) {
+ file_.reset(nullptr);
+ return false;
}
+ return true;
+ }
- bool finishEntry() override {
- if (!mFile) {
- return false;
- }
- mFile.reset(nullptr);
- return true;
+ bool FinishEntry() override {
+ if (!file_) {
+ return false;
}
+ file_.reset(nullptr);
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
+
+ std::string dir_;
+ std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
};
-struct ZipFileWriter : public IArchiveWriter {
- std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
- std::unique_ptr<ZipWriter> mWriter;
+class ZipFileWriter : public IArchiveWriter {
+ public:
+ ZipFileWriter() = default;
- 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) {
+ file_ = {fopen(path.data(), "w+b"), fclose};
+ if (!file_) {
+ diag->Error(DiagMessage() << "failed to Open " << path << ": "
+ << strerror(errno));
+ return false;
+ }
+ writer_ = util::make_unique<ZipWriter>(file_.get());
+ return true;
+ }
+
+ bool StartEntry(const StringPiece& path, uint32_t flags) override {
+ if (!writer_) {
+ 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 zip_flags = 0;
+ if (flags & ArchiveEntry::kCompress) {
+ zip_flags |= 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) {
+ zip_flags |= 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 = writer_->StartEntry(path.data(), zip_flags);
+ 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 = writer_->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 = writer_->WriteBytes(b.buffer.get(), b.size);
+ if (result != 0) {
+ return false;
+ }
}
+ return true;
+ }
+
+ bool FinishEntry() override {
+ int32_t result = writer_->FinishEntry();
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ virtual ~ZipFileWriter() {
+ if (writer_) {
+ writer_->Finish();
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ZipFileWriter);
+
+ std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+ std::unique_ptr<ZipWriter> writer_;
};
-} // 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..4fcb3ffa 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -17,51 +17,52 @@
#ifndef AAPT_FLATTEN_ARCHIVE_H
#define AAPT_FLATTEN_ARCHIVE_H
-#include "Diagnostics.h"
-#include "util/BigBuffer.h"
-#include "util/Files.h"
-#include "util/StringPiece.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+#include "Diagnostics.h"
+#include "util/BigBuffer.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
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 uncompressed_size;
};
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..968d3ee 100644
--- a/tools/aapt2/flatten/ChunkWriter.h
+++ b/tools/aapt2/flatten/ChunkWriter.h
@@ -17,71 +17,64 @@
#ifndef AAPT_FLATTEN_CHUNKWRITER_H
#define AAPT_FLATTEN_CHUNKWRITER_H
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
#include "util/BigBuffer.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-
namespace aapt {
class ChunkWriter {
-private:
- BigBuffer* mBuffer;
- size_t mStartSize = 0;
- android::ResChunk_header* mHeader = nullptr;
+ public:
+ explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {}
+ ChunkWriter(ChunkWriter&&) = default;
+ ChunkWriter& operator=(ChunkWriter&&) = default;
-public:
- explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ template <typename T>
+ inline T* StartChunk(uint16_t type) {
+ start_size_ = buffer_->size();
+ T* chunk = buffer_->NextBlock<T>();
+ header_ = &chunk->header;
+ header_->type = util::HostToDevice16(type);
+ header_->headerSize = util::HostToDevice16(sizeof(T));
+ return chunk;
+ }
- ChunkWriter(const ChunkWriter&) = delete;
- ChunkWriter& operator=(const ChunkWriter&) = delete;
- ChunkWriter(ChunkWriter&&) = default;
- ChunkWriter& operator=(ChunkWriter&&) = default;
+ template <typename T>
+ inline T* NextBlock(size_t count = 1) {
+ return buffer_->NextBlock<T>(count);
+ }
- 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;
- }
+ inline BigBuffer* buffer() { return buffer_; }
- template <typename T>
- inline T* nextBlock(size_t count = 1) {
- return mBuffer->nextBlock<T>(count);
- }
+ inline android::ResChunk_header* chunk_header() { return header_; }
- inline BigBuffer* getBuffer() {
- return mBuffer;
- }
+ inline size_t size() { return buffer_->size() - start_size_; }
- inline android::ResChunk_header* getChunkHeader() {
- return mHeader;
- }
+ inline android::ResChunk_header* Finish() {
+ buffer_->Align4();
+ header_->size = util::HostToDevice32(buffer_->size() - start_size_);
+ return header_;
+ }
- inline size_t size() {
- return mBuffer->size() - mStartSize;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChunkWriter);
- inline android::ResChunk_header* finish() {
- mBuffer->align4();
- mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
- return mHeader;
- }
+ BigBuffer* buffer_;
+ size_t start_size_ = 0;
+ android::ResChunk_header* header_ = nullptr;
};
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;
+inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) {
+ start_size_ = buffer_->size();
+ header_ = buffer_->NextBlock<android::ResChunk_header>();
+ header_->type = util::HostToDevice16(type);
+ header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header));
+ return header_;
}
-} // 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..6359b41 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -17,20 +17,21 @@
#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H
#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/ResourceTypes.h"
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..19d030e 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -14,21 +14,23 @@
* limitations under the License.
*/
+#include "flatten/TableFlattener.h"
+
+#include <algorithm>
+#include <numeric>
+#include <sstream>
+#include <type_traits>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-
#include "flatten/ChunkWriter.h"
#include "flatten/ResourceTypeExtensions.h"
-#include "flatten/TableFlattener.h"
#include "util/BigBuffer.h"
-#include <android-base/macros.h>
-#include <algorithm>
-#include <sstream>
-#include <type_traits>
-#include <numeric>
-
using namespace android;
namespace aapt {
@@ -36,439 +38,458 @@
namespace {
template <typename T>
-static bool cmpIds(const T* a, const T* b) {
- return a->id.value() < b->id.value();
+static bool cmp_ids(const T* a, const T* b) {
+ 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* src_data = src.data();
+ for (i = 0; i < len - 1 && i < src.size(); i++) {
+ dst[i] = util::HostToDevice16((uint16_t)src_data[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;
+static bool cmp_style_entries(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;
}
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 entry_key;
};
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* out_entry, BigBuffer* buffer)
+ : out_entry_(out_entry), buffer_(buffer) {}
+
+ void Visit(Attribute* attr) override {
+ {
+ Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
+ 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->min_int != 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->min_int));
+ 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->max_int != 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->max_int));
+ 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& parent_ref = style->parent.value();
+ CHECK(bool(parent_ref.id)) << "parent has no ID";
+ out_entry_->parent.ident = util::HostToDevice32(parent_ref.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(), cmp_style_entries);
+
+ 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& attr_ref : styleable->entries) {
+ BinaryPrimitive val(Res_value{});
+ FlattenEntry(&attr_ref, &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* out_entry = buffer_->NextBlock<ResTable_map>();
+ FlattenValue(item.get(), out_entry);
+ out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
+ entry_count_++;
}
+ }
-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:
+ LOG(FATAL) << "unhandled plural type";
+ 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() { out_entry_->count = util::HostToDevice32(entry_count_); }
- 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:
+ DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
- ResTable_entry_ext* mOutEntry;
- BigBuffer* mBuffer;
- size_t mEntryCount = 0;
+ void FlattenKey(Reference* key, ResTable_map* out_entry) {
+ CHECK(bool(key->id)) << "key has no ID";
+ out_entry->name.ident = util::HostToDevice32(key->id.value().id);
+ }
+
+ void FlattenValue(Item* value, ResTable_map* out_entry) {
+ CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
+ }
+
+ void FlattenEntry(Reference* key, Item* value) {
+ ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
+ FlattenKey(key, out_entry);
+ FlattenValue(value, out_entry);
+ out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
+ entry_count_++;
+ }
+
+ ResTable_entry_ext* out_entry_;
+ BigBuffer* buffer_;
+ size_t entry_count_ = 0;
};
class PackageFlattener {
-public:
- PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
- mDiag(diag), mPackage(package) {
+ public:
+ PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
+ : diag_(diag), package_(package) {}
+
+ bool FlattenPackage(BigBuffer* buffer) {
+ ChunkWriter pkg_writer(buffer);
+ ResTable_package* pkg_header =
+ pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
+ pkg_header->id = util::HostToDevice32(package_->id.value());
+
+ if (package_->name.size() >= arraysize(pkg_header->name)) {
+ diag_->Error(DiagMessage() << "package name '" << package_->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(pkg_header->name, arraysize(pkg_header->name),
+ util::Utf8ToUtf16(package_->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 type_buffer(1024);
+ FlattenTypes(&type_buffer);
- // Copy the package name in device endianness.
- strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name),
- util::utf8ToUtf16(mPackage->name));
+ pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size());
+ StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_);
- // 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);
+ pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size());
+ StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_);
- pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
- StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+ // Append the types.
+ buffer->AppendBuffer(std::move(type_buffer));
- pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
- StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool);
+ pkg_writer.Finish();
+ return true;
+ }
- // Append the types.
- buffer->appendBuffer(std::move(typeBuffer));
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
- 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* out_entry = (ResTable_entry*)result;
+ if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+ out_entry->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()) {
+ out_entry->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) {
+ out_entry->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;
- }
+ out_entry->flags = util::HostToDevice16(out_entry->flags);
+ out_entry->key.index = util::HostToDevice32(entry->entry_key);
+ out_entry->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>();
+ CHECK(item->Flatten(outValue)) << "flatten failed";
+ outValue->size = util::HostToDevice16(sizeof(*outValue));
+ } else {
+ ResTable_entry_ext* out_entry =
+ WriteEntry<ResTable_entry_ext, false>(entry, buffer);
+ MapFlattenVisitor visitor(out_entry, 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 type_writer(buffer);
+ ResTable_type* type_header =
+ type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+ type_header->id = type->id.value();
+ type_header->config = config;
+ type_header->config.swapHtoD();
+
+ auto max_accum = [](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 entry_count =
+ std::accumulate(type->entries.begin(), type->entries.end(), 0,
+ max_accum) +
+ 1;
+
+ type_header->entryCount = util::HostToDevice32(entry_count);
+ uint32_t* indices = type_writer.NextBlock<uint32_t>(entry_count);
+
+ CHECK((size_t)entry_count <= std::numeric_limits<uint16_t>::max());
+ memset(indices, 0xff, entry_count * sizeof(uint32_t));
+
+ type_header->entriesStart = util::HostToDevice32(type_writer.size());
+
+ const size_t entry_start = type_writer.buffer()->size();
+ for (FlatEntry& flat_entry : *entries) {
+ CHECK(flat_entry.entry->id.value() < entry_count);
+ indices[flat_entry.entry->id.value()] =
+ util::HostToDevice32(type_writer.buffer()->size() - entry_start);
+ if (!FlattenValue(&flat_entry, type_writer.buffer())) {
+ diag_->Error(DiagMessage()
+ << "failed to flatten resource '"
+ << ResourceNameRef(package_->name, type->type,
+ flat_entry.entry->name)
+ << "' for configuration '" << config << "'");
+ return false;
+ }
+ }
+ type_writer.Finish();
+ return true;
+ }
+
+ std::vector<ResourceTableType*> CollectAndSortTypes() {
+ std::vector<ResourceTableType*> sorted_types;
+ for (auto& type : package_->types) {
+ if (type->type == ResourceType::kStyleable) {
+ // Styleables aren't real Resource Types, they are represented in the
+ // R.java file.
+ continue;
+ }
+
+ CHECK(bool(type->id)) << "type must have an ID set";
+
+ sorted_types.push_back(type.get());
+ }
+ std::sort(sorted_types.begin(), sorted_types.end(),
+ cmp_ids<ResourceTableType>);
+ return sorted_types;
+ }
+
+ std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
+ // Sort the entries by entry ID.
+ std::vector<ResourceEntry*> sorted_entries;
+ for (auto& entry : type->entries) {
+ CHECK(bool(entry->id)) << "entry must have an ID set";
+ sorted_entries.push_back(entry.get());
+ }
+ std::sort(sorted_entries.begin(), sorted_entries.end(),
+ cmp_ids<ResourceEntry>);
+ return sorted_entries;
+ }
+
+ bool FlattenTypeSpec(ResourceTableType* type,
+ std::vector<ResourceEntry*>* sorted_entries,
+ BigBuffer* buffer) {
+ ChunkWriter type_spec_writer(buffer);
+ ResTable_typeSpec* spec_header =
+ type_spec_writer.StartChunk<ResTable_typeSpec>(
+ RES_TABLE_TYPE_SPEC_TYPE);
+ spec_header->id = type->id.value();
+
+ if (sorted_entries->empty()) {
+ type_spec_writer.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 num_entries = sorted_entries->back()->id.value() + 1;
+
+ spec_header->entryCount = util::HostToDevice32(num_entries);
+
+ // Reserve space for the masks of each resource in this type. These
+ // show for which configuration axis the resource changes.
+ uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
+
+ const size_t actual_num_entries = sorted_entries->size();
+ for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
+ ResourceEntry* entry = sorted_entries->at(entryIndex);
+
+ // Populate the config masks for this entry.
+
+ if (entry->symbol_status.state == SymbolState::kPublic) {
+ config_masks[entry->id.value()] |=
+ util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+ }
+
+ const size_t config_count = entry->values.size();
+ for (size_t i = 0; i < config_count; i++) {
+ const ConfigDescription& config = entry->values[i]->config;
+ for (size_t j = i + 1; j < config_count; j++) {
+ config_masks[entry->id.value()] |=
+ util::HostToDevice32(config.diff(entry->values[j]->config));
}
- std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
- return sortedEntries;
+ }
}
+ type_spec_writer.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*> sorted_types = CollectAndSortTypes();
- if (sortedEntries->empty()) {
- typeSpecWriter.finish();
- return true;
+ size_t expected_type_id = 1;
+ for (ResourceTableType* type : sorted_types) {
+ // 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() > expected_type_id) {
+ std::stringstream type_name;
+ type_name << "?" << expected_type_id;
+ type_pool_.MakeRef(type_name.str());
+ expected_type_id++;
+ }
+ expected_type_id++;
+ type_pool_.MakeRef(ToString(type->type));
+
+ std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
+
+ if (!FlattenTypeSpec(type, &sorted_entries, 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>>
+ config_to_entry_list_map;
+ for (ResourceEntry* entry : sorted_entries) {
+ const uint32_t key_index =
+ (uint32_t)key_pool_.MakeRef(entry->name).index();
+
+ // Group values by configuration.
+ for (auto& config_value : entry->values) {
+ config_to_entry_list_map[config_value->config].push_back(
+ FlatEntry{entry, config_value->value.get(), key_index});
}
+ }
- // 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 : config_to_entry_list_map) {
+ if (!FlattenConfig(type, entry.first, &entry.second, buffer)) {
+ return false;
}
- typeSpecWriter.finish();
- return true;
+ }
}
+ 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;
- }
+ IDiagnostics* diag_;
+ ResourceTablePackage* package_;
+ StringPool type_pool_;
+ StringPool key_pool_;
};
-} // 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 {
+bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
+ // We must do this before writing the resources, since the string pool IDs may
+ // change.
+ table->string_pool.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 +497,32 @@
if (diff < 0) return true;
if (diff > 0) return false;
return a.value < b.value;
- });
- table->stringPool.prune();
+ });
+ table->string_pool.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 table_writer(buffer_);
+ ResTable_header* table_header =
+ table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
+ table_header->packageCount = util::HostToDevice32(table->packages.size());
- // Flatten the values string pool.
- StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
+ // Flatten the values string pool.
+ StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
- BigBuffer packageBuffer(1024);
+ BigBuffer package_buffer(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(&package_buffer)) {
+ 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.
+ table_writer.buffer()->AppendBuffer(std::move(package_buffer));
+ table_writer.Finish();
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
index b416f20..53f52c2 100644
--- a/tools/aapt2/flatten/TableFlattener.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -17,24 +17,26 @@
#ifndef AAPT_FLATTEN_TABLEFLATTENER_H
#define AAPT_FLATTEN_TABLEFLATTENER_H
+#include "android-base/macros.h"
+
+#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
namespace aapt {
-class BigBuffer;
-class ResourceTable;
-
class TableFlattener : public IResourceTableConsumer {
-public:
- explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ public:
+ explicit TableFlattener(BigBuffer* buffer) : buffer_(buffer) {}
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
-private:
- BigBuffer* mBuffer;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TableFlattener);
+
+ BigBuffer* buffer_;
};
-} // 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..c726240 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -14,8 +14,9 @@
* 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 +26,211 @@
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 {
+ context_ = test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .Build();
+ }
+
+ ::testing::AssertionResult Flatten(ResourceTable* table,
+ ResTable* out_table) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.Consume(context_.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 (out_table->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* out_table) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.Consume(context_.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(context_.get(), out_table, {}, 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& expected_name,
+ const ResourceId& expected_id,
+ const ConfigDescription& expected_config,
+ const uint8_t expected_data_type,
+ const uint32_t expected_data,
+ const uint32_t expected_spec_flags) {
+ const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
+
+ table->setParameters(&expected_config);
+
+ ResTable_config config;
+ Res_value val;
+ uint32_t spec_flags;
+ if (table->getResource(expected_id.id, &val, false, 0, &spec_flags,
+ &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 (expected_data_type != val.dataType) {
+ return ::testing::AssertionFailure()
+ << "expected data type " << std::hex << (int)expected_data_type
+ << " but got data type " << (int)val.dataType << std::dec
+ << " instead";
}
-private:
- std::unique_ptr<IAaptContext> mContext;
+ if (expected_data != val.data) {
+ return ::testing::AssertionFailure()
+ << "expected data " << std::hex << expected_data
+ << " but got data " << val.data << std::dec << " instead";
+ }
+
+ if (expected_spec_flags != spec_flags) {
+ return ::testing::AssertionFailure()
+ << "expected specFlags " << std::hex << expected_spec_flags
+ << " but got specFlags " << spec_flags << std::dec << " instead";
+ }
+
+ ResTable::resource_name actual_name;
+ if (!table->getResourceName(expected_id.id, false, &actual_name)) {
+ return ::testing::AssertionFailure() << "failed to find resource name";
+ }
+
+ Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
+ if (!resName) {
+ return ::testing::AssertionFailure()
+ << "expected name '" << expected_res_name << "' but got '"
+ << StringPiece16(actual_name.package, actual_name.packageLen)
+ << ":" << StringPiece16(actual_name.type, actual_name.typeLen)
+ << "/" << StringPiece16(actual_name.name, actual_name.nameLen)
+ << "'";
+ }
+
+ if (expected_config != config) {
+ return ::testing::AssertionFailure() << "expected config '"
+ << expected_config << "' but got '"
+ << ConfigDescription(config) << "'";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ private:
+ std::unique_ptr<IAaptContext> context_;
};
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 res_table;
+ ASSERT_TRUE(Flatten(table.get(), &res_table));
- EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000), {},
- Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(Exists(&res_table, "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(&res_table, "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(&res_table, "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(&res_table, "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(&res_table, "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 foo_str = u"foo";
+ ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(),
+ foo_str.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "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 bar_path = u"res/layout/bar.xml";
+ idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(),
+ bar_path.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "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 res_table;
+ ASSERT_TRUE(Flatten(table.get(), &res_table));
- 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(&res_table, "com.app.test:id/one", ResourceId(0x7f020001),
+ {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+ EXPECT_TRUE(Exists(&res_table, "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.type_mask = android::ResTable_map::TYPE_INTEGER;
+ attr.min_int = 10;
+ attr.max_int = 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.type_mask, actualAttr->type_mask);
+ EXPECT_EQ(attr.min_int, actualAttr->min_int);
+ EXPECT_EQ(attr.max_int, actualAttr->max_int);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index ed5b60f..366c373 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -14,18 +14,22 @@
* limitations under the License.
*/
+#include "flatten/XmlFlattener.h"
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/misc.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 <algorithm>
-#include <map>
-#include <utils/misc.h>
-#include <vector>
-
using namespace android;
namespace aapt {
@@ -34,301 +38,323 @@
constexpr uint32_t kLowPriority = 0xffffffffu;
-struct XmlFlattenerVisitor : public xml::Visitor {
- using xml::Visitor::visit;
+static bool cmp_xml_attribute_by_id(const xml::Attribute* a,
+ const xml::Attribute* b) {
+ if (a->compiled_attribute && a->compiled_attribute.value().id) {
+ if (b->compiled_attribute && b->compiled_attribute.value().id) {
+ return a->compiled_attribute.value().id.value() <
+ b->compiled_attribute.value().id.value();
+ }
+ return true;
+ } else if (!b->compiled_attribute) {
+ int diff = a->namespace_uri.compare(b->namespace_uri);
+ if (diff < 0) {
+ return true;
+ } else if (diff > 0) {
+ return false;
+ }
+ return a->name < b->name;
+ }
+ return false;
+}
- BigBuffer* mBuffer;
- XmlFlattenerOptions mOptions;
- StringPool mPool;
- std::map<uint8_t, StringPool> mPackagePools;
+class XmlFlattenerVisitor : public xml::Visitor {
+ public:
+ using xml::Visitor::Visit;
- struct StringFlattenDest {
- StringPool::Ref ref;
- ResStringPool_ref* dest;
- };
- std::vector<StringFlattenDest> mStringRefs;
+ StringPool pool;
+ std::map<uint8_t, StringPool> package_pools;
- // Scratch vector to filter attributes. We avoid allocations
- // making this a member.
- std::vector<xml::Attribute*> mFilteredAttrs;
+ struct StringFlattenDest {
+ StringPool::Ref ref;
+ ResStringPool_ref* dest;
+ };
+ std::vector<StringFlattenDest> string_refs;
- XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
- mBuffer(buffer), mOptions(options) {
+ XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
+ : buffer_(buffer), options_(options) {}
+
+ void Visit(xml::Namespace* node) override {
+ if (node->namespace_uri == 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(buffer_);
+ ResXMLTree_node* flat_node =
+ writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+ flat_node->lineNumber = util::HostToDevice32(node->line_number);
+ flat_node->comment.index = util::HostToDevice32(-1);
+
+ ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
+ AddString(node->text, kLowPriority, &flat_text->data);
+
+ writer.Finish();
+ }
+
+ void Visit(xml::Element* node) override {
+ {
+ ChunkWriter start_writer(buffer_);
+ ResXMLTree_node* flat_node =
+ start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
+ flat_node->lineNumber = util::HostToDevice32(node->line_number);
+ flat_node->comment.index = util::HostToDevice32(-1);
+
+ ResXMLTree_attrExt* flat_elem =
+ start_writer.NextBlock<ResXMLTree_attrExt>();
+
+ // A missing namespace must be null, not an empty string. Otherwise the
+ // runtime complains.
+ AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,
+ true /* treat_empty_string_as_null */);
+ AddString(node->name, kLowPriority, &flat_elem->name,
+ true /* treat_empty_string_as_null */);
+
+ flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem));
+ flat_elem->attributeSize =
+ util::HostToDevice16(sizeof(ResXMLTree_attribute));
+
+ WriteAttributes(node, flat_elem, &start_writer);
+
+ start_writer.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 end_writer(buffer_);
+ ResXMLTree_node* flat_end_node =
+ end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
+ flat_end_node->lineNumber = util::HostToDevice32(node->line_number);
+ flat_end_node->comment.index = util::HostToDevice32(-1);
- *resIdMapWriter.nextBlock<uint32_t>() = id.id;
+ ResXMLTree_endElementExt* flat_end_elem =
+ end_writer.NextBlock<ResXMLTree_endElementExt>();
+ AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,
+ true /* treat_empty_string_as_null */);
+ AddString(node->name, kLowPriority, &flat_end_elem->name);
+
+ end_writer.Finish();
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
+
+ void AddString(const StringPiece& str, uint32_t priority,
+ android::ResStringPool_ref* dest,
+ bool treat_empty_string_as_null = false) {
+ if (str.empty() && treat_empty_string_as_null) {
+ // Some parts of the runtime treat null differently than empty string.
+ dest->index = util::DeviceToHost32(-1);
+ } else {
+ string_refs.push_back(StringFlattenDest{
+ pool.MakeRef(str, StringPool::Context(priority)), dest});
+ }
+ }
+
+ void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+ string_refs.push_back(StringFlattenDest{ref, dest});
+ }
+
+ void WriteNamespace(xml::Namespace* node, uint16_t type) {
+ ChunkWriter writer(buffer_);
+
+ ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type);
+ flatNode->lineNumber = util::HostToDevice32(node->line_number);
+ flatNode->comment.index = util::HostToDevice32(-1);
+
+ ResXMLTree_namespaceExt* flat_ns =
+ writer.NextBlock<ResXMLTree_namespaceExt>();
+ AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix);
+ AddString(node->namespace_uri, kLowPriority, &flat_ns->uri);
+
+ writer.Finish();
+ }
+
+ void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem,
+ ChunkWriter* writer) {
+ filtered_attrs_.clear();
+ filtered_attrs_.reserve(node->attributes.size());
+
+ // Filter the attributes.
+ for (xml::Attribute& attr : node->attributes) {
+ if (options_.max_sdk_level && attr.compiled_attribute &&
+ attr.compiled_attribute.value().id) {
+ size_t sdk_level =
+ FindAttributeSdkLevel(attr.compiled_attribute.value().id.value());
+ if (sdk_level > options_.max_sdk_level.value()) {
+ continue;
}
- resIdMapWriter.finish();
+ }
+ if (attr.namespace_uri == xml::kSchemaTools) {
+ continue;
+ }
+ filtered_attrs_.push_back(&attr);
}
- // 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;
+ if (filtered_attrs_.empty()) {
+ return;
}
- return flatten(context, resource->root.get());
+
+ const ResourceId kIdAttr(0x010100d0);
+
+ std::sort(filtered_attrs_.begin(), filtered_attrs_.end(),
+ cmp_xml_attribute_by_id);
+
+ flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size());
+
+ ResXMLTree_attribute* flat_attr =
+ writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size());
+ uint16_t attribute_index = 1;
+ for (const xml::Attribute* xml_attr : filtered_attrs_) {
+ // Assign the indices for specific attributes.
+ if (xml_attr->compiled_attribute &&
+ xml_attr->compiled_attribute.value().id &&
+ xml_attr->compiled_attribute.value().id.value() == kIdAttr) {
+ flat_elem->idIndex = util::HostToDevice16(attribute_index);
+ } else if (xml_attr->namespace_uri.empty()) {
+ if (xml_attr->name == "class") {
+ flat_elem->classIndex = util::HostToDevice16(attribute_index);
+ } else if (xml_attr->name == "style") {
+ flat_elem->styleIndex = util::HostToDevice16(attribute_index);
+ }
+ }
+ attribute_index++;
+
+ // Add the namespaceUri to the list of StringRefs to encode. Use null if
+ // the namespace
+ // is empty (doesn't exist).
+ AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns,
+ true /* treat_empty_string_as_null */);
+
+ flat_attr->rawValue.index = util::HostToDevice32(-1);
+
+ if (!xml_attr->compiled_attribute ||
+ !xml_attr->compiled_attribute.value().id) {
+ // The attribute has no associated ResourceID, so the string order
+ // doesn't matter.
+ AddString(xml_attr->name, kLowPriority, &flat_attr->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& aapt_attr =
+ xml_attr->compiled_attribute.value();
+
+ StringPool::Ref name_ref =
+ package_pools[aapt_attr.id.value().package_id()].MakeRef(
+ xml_attr->name, StringPool::Context(aapt_attr.id.value().id));
+
+ // Add it to the list of strings to flatten.
+ AddString(name_ref, &flat_attr->name);
+ }
+
+ if (options_.keep_raw_values || !xml_attr->compiled_value) {
+ // Keep raw values if the value is not compiled or
+ // if we're building a static library (need symbols).
+ AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue);
+ }
+
+ if (xml_attr->compiled_value) {
+ CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
+ } else {
+ // Flatten as a regular string type.
+ flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ AddString(xml_attr->value, kLowPriority,
+ (ResStringPool_ref*)&flat_attr->typedValue.data);
+ }
+
+ flat_attr->typedValue.size =
+ util::HostToDevice16(sizeof(flat_attr->typedValue));
+ flat_attr++;
+ }
+ }
+
+ BigBuffer* buffer_;
+ XmlFlattenerOptions options_;
+
+ // Scratch vector to filter attributes. We avoid allocations
+ // making this a member.
+ std::vector<xml::Attribute*> filtered_attrs_;
+};
+
+} // namespace
+
+bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
+ BigBuffer node_buffer(1024);
+ XmlFlattenerVisitor visitor(&node_buffer, options_);
+ node->Accept(&visitor);
+
+ // Merge the package pools into the main pool.
+ for (auto& package_pool_entry : visitor.package_pools) {
+ visitor.pool.Merge(std::move(package_pool_entry.second));
+ }
+
+ // Sort the string pool so that attribute resource IDs show up first.
+ visitor.pool.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& ref_entry : visitor.string_refs) {
+ ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index());
+ }
+
+ // Write the XML header.
+ ChunkWriter xml_header_writer(buffer_);
+ xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+ // Flatten the StringPool.
+ StringPool::FlattenUtf8(buffer_, visitor.pool);
+
+ {
+ // Write the array of resource IDs, indexed by StringPool order.
+ ChunkWriter res_id_map_writer(buffer_);
+ res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+ for (const auto& str : visitor.pool) {
+ ResourceId id = {str->context.priority};
+ if (id.id == kLowPriority || !id.is_valid()) {
+ // When we see the first non-resource ID,
+ // we're done.
+ break;
+ }
+
+ *res_id_map_writer.NextBlock<uint32_t>() = id.id;
+ }
+ res_id_map_writer.Finish();
+ }
+
+ // Move the nodeBuffer and append it to the out buffer.
+ buffer_->AppendBuffer(std::move(node_buffer));
+
+ // Finish the xml header.
+ xml_header_writer.Finish();
+ return true;
}
-} // namespace aapt
+bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
+ if (!resource->root) {
+ return false;
+ }
+ return Flatten(context, resource->root.get());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index a688ac9..f5129fd 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -17,6 +17,8 @@
#ifndef AAPT_FLATTEN_XMLFLATTENER_H
#define AAPT_FLATTEN_XMLFLATTENER_H
+#include "android-base/macros.h"
+
#include "process/IResourceTableConsumer.h"
#include "util/BigBuffer.h"
#include "xml/XmlDom.h"
@@ -24,32 +26,33 @@
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 keep_raw_values = 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> max_sdk_level;
};
class XmlFlattener : public IXmlResourceConsumer {
-public:
- XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
- mBuffer(buffer), mOptions(options) {
- }
+ public:
+ XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
+ : buffer_(buffer), options_(options) {}
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
-private:
- BigBuffer* mBuffer;
- XmlFlattenerOptions mOptions;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
- bool flatten(IAaptContext* context, xml::Node* node);
+ bool Flatten(IAaptContext* context, xml::Node* node);
+
+ BigBuffer* buffer_;
+ XmlFlattenerOptions options_;
};
-} // 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..2c83bb3 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -15,240 +15,251 @@
*/
#include "flatten/XmlFlattener.h"
+
+#include "androidfw/ResourceTypes.h"
+
#include "link/Linkers.h"
#include "test/Test.h"
#include "util/BigBuffer.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-
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 {
+ context_ =
+ 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* out_tree,
+ 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(context_.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 (out_tree->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> context_;
};
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* namespace_prefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test");
- size_t len;
- const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+ const char16_t* namespace_uri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespace_uri, 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* tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, 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* attr_name = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attr_name, 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);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, 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* attr_namespace = tree.getAttributeNamespace(0, &len);
+ EXPECT_EQ(StringPiece16(attr_namespace, 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");
+ attr_name = tree.getAttributeName(0, &len);
+ EXPECT_EQ(StringPiece16(attr_name, 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);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, 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);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, 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);
+ tag_name = tree.getElementName(&len);
+ EXPECT_EQ(StringPiece16(tag_name, 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);
+ namespace_prefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test");
- ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
- namespacePrefix = tree.getNamespacePrefix(&len);
- EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
+ namespace_uri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespace_uri, 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(context_.get(), doc.get()));
+ ASSERT_TRUE(linker.sdk_levels().count(17) == 1);
+ ASSERT_TRUE(linker.sdk_levels().count(21) == 1);
- android::ResXMLTree tree;
- XmlFlattenerOptions options;
- options.maxSdkLevel = 17;
- ASSERT_TRUE(flatten(doc.get(), &tree, options));
+ android::ResXMLTree tree;
+ XmlFlattenerOptions options;
+ options.max_sdk_level = 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* namespace_prefix = tree.getNamespacePrefix(&len);
+ EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo");
- const char16_t* namespaceUri = tree.getNamespaceUri(&len);
- ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://schemas.android.com/foo");
+ const char16_t* namespace_uri = tree.getNamespaceUri(&len);
+ ASSERT_EQ(StringPiece16(namespace_uri, 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/integration-tests/AppOne/res/drawable/cheap_transparency.png b/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png
new file mode 100644
index 0000000..0522a99
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png
new file mode 100644
index 0000000..baf9fff
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png
new file mode 100644
index 0000000..7b331e1
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png
new file mode 100644
index 0000000..0ec6c70
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png
new file mode 100644
index 0000000..e05708a
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png
new file mode 100644
index 0000000..a11377a
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png
new file mode 100644
index 0000000..6803e42
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png
new file mode 100644
index 0000000..1a3731b
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png
new file mode 100644
index 0000000..489ace2
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png
Binary files differ
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 34eed63..fdc044d 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -17,106 +17,97 @@
#ifndef AAPT_IO_DATA_H
#define AAPT_IO_DATA_H
-#include <android-base/macros.h>
#include <memory>
-#include <utils/FileMap.h>
+
+#include "android-base/macros.h"
+#include "utils/FileMap.h"
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)
+ : data_(std::move(data)), offset_(offset), len_(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*>(data_->data()) + offset_;
+ }
- size_t size() const override {
- return mLen;
- }
+ size_t size() const override { return len_; }
-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> data_;
+ size_t offset_;
+ size_t len_;
};
/**
- * 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)
+ : map_(std::forward<android::FileMap>(map)) {}
- const void* data() const override {
- return mMap.getDataPtr();
- }
+ const void* data() const override { return map_.getDataPtr(); }
- size_t size() const override {
- return mMap.getDataLength();
- }
+ size_t size() const override { return map_.getDataLength(); }
-private:
- android::FileMap mMap;
+ private:
+ android::FileMap map_;
};
/**
- * 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)
+ : data_(std::move(data)), size_(size) {}
- const void* data() const override {
- return mData.get();
- }
+ const void* data() const override { return data_.get(); }
- size_t size() const override {
- return mSize;
- }
+ size_t size() const override { return size_; }
-private:
- std::unique_ptr<const uint8_t[]> mData;
- size_t mSize;
+ private:
+ std::unique_ptr<const uint8_t[]> data_;
+ size_t size_;
};
/**
- * 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.cpp b/tools/aapt2/io/File.cpp
index 739c0d2..ee73728 100644
--- a/tools/aapt2/io/File.cpp
+++ b/tools/aapt2/io/File.cpp
@@ -21,23 +21,23 @@
namespace aapt {
namespace io {
-IFile* IFile::createFileSegment(size_t offset, size_t len) {
- FileSegment* fileSegment = new FileSegment(this, offset, len);
- mSegments.push_back(std::unique_ptr<IFile>(fileSegment));
- return fileSegment;
+IFile* IFile::CreateFileSegment(size_t offset, size_t len) {
+ FileSegment* file_segment = new FileSegment(this, offset, len);
+ segments_.push_back(std::unique_ptr<IFile>(file_segment));
+ return file_segment;
}
-std::unique_ptr<IData> FileSegment::openAsData() {
- std::unique_ptr<IData> data = mFile->openAsData();
- if (!data) {
- return {};
- }
-
- if (mOffset <= data->size() - mLen) {
- return util::make_unique<DataSegment>(std::move(data), mOffset, mLen);
- }
+std::unique_ptr<IData> FileSegment::OpenAsData() {
+ std::unique_ptr<IData> data = file_->OpenAsData();
+ if (!data) {
return {};
+ }
+
+ if (offset_ <= data->size() - len_) {
+ return util::make_unique<DataSegment>(std::move(data), offset_, len_);
+ }
+ return {};
}
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 807981e..644f59f 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -17,96 +17,104 @@
#ifndef AAPT_IO_FILE_H
#define AAPT_IO_FILE_H
-#include "Source.h"
-#include "io/Data.h"
-#include "util/Util.h"
-
-#include <android-base/macros.h>
#include <list>
#include <memory>
#include <vector>
+#include "android-base/macros.h"
+
+#include "Source.h"
+#include "io/Data.h"
+#include "util/Util.h"
+
namespace aapt {
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>> segments_;
};
/**
- * 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)
+ : file_(file), offset_(offset), len_(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 file_->GetSource(); }
-private:
- DISALLOW_COPY_AND_ASSIGN(FileSegment);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileSegment);
- IFile* mFile;
- size_t mOffset;
- size_t mLen;
+ IFile* file_;
+ size_t offset_;
+ size_t len_;
};
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.cpp b/tools/aapt2/io/FileSystem.cpp
index e758d8a4..828f34e 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -14,65 +14,62 @@
* limitations under the License.
*/
-#include "Source.h"
#include "io/FileSystem.h"
+
+#include "utils/FileMap.h"
+
+#include "Source.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-#include <utils/FileMap.h>
-
namespace aapt {
namespace io {
-RegularFile::RegularFile(const Source& source) : mSource(source) {
-}
+RegularFile::RegularFile(const Source& source) : source_(source) {}
-std::unique_ptr<IData> RegularFile::openAsData() {
- android::FileMap map;
- if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
- if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
- return util::make_unique<MmappedData>(std::move(map.value()));
- }
- return util::make_unique<EmptyData>();
+std::unique_ptr<IData> RegularFile::OpenAsData() {
+ android::FileMap map;
+ if (Maybe<android::FileMap> map = file::MmapPath(source_.path, nullptr)) {
+ if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
}
- return {};
+ return util::make_unique<EmptyData>();
+ }
+ return {};
}
-const Source& RegularFile::getSource() const {
- return mSource;
+const Source& RegularFile::GetSource() const { return source_; }
+
+FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
+ : current_(collection->files_.begin()), end_(collection->files_.end()) {}
+
+bool FileCollectionIterator::HasNext() { return current_ != end_; }
+
+IFile* FileCollectionIterator::Next() {
+ IFile* result = current_->second.get();
+ ++current_;
+ return result;
}
-FileCollectionIterator::FileCollectionIterator(FileCollection* collection) :
- mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
+IFile* FileCollection::InsertFile(const StringPiece& path) {
+ return (files_[path.ToString()] =
+ util::make_unique<RegularFile>(Source(path)))
+ .get();
}
-bool FileCollectionIterator::hasNext() {
- return mCurrent != mEnd;
+IFile* FileCollection::FindFile(const StringPiece& path) {
+ auto iter = files_.find(path.ToString());
+ if (iter != files_.end()) {
+ return iter->second.get();
+ }
+ return nullptr;
}
-IFile* FileCollectionIterator::next() {
- IFile* result = mCurrent->second.get();
- ++mCurrent;
- return result;
+std::unique_ptr<IFileCollectionIterator> FileCollection::Iterator() {
+ return util::make_unique<FileCollectionIterator>(this);
}
-IFile* FileCollection::insertFile(const StringPiece& path) {
- return (mFiles[path.toString()] = util::make_unique<RegularFile>(Source(path))).get();
-}
-
-IFile* FileCollection::findFile(const StringPiece& path) {
- auto iter = mFiles.find(path.toString());
- if (iter != mFiles.end()) {
- return iter->second.get();
- }
- return nullptr;
-}
-
-std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() {
- return util::make_unique<FileCollectionIterator>(this);
-}
-
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index 72a932a..84f851f 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -17,10 +17,10 @@
#ifndef AAPT_IO_FILESYSTEM_H
#define AAPT_IO_FILESYSTEM_H
-#include "io/File.h"
-
#include <map>
+#include "io/File.h"
+
namespace aapt {
namespace io {
@@ -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 source_;
};
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 current_, end_;
};
/**
* 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>> files_;
};
-} // 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.cpp b/tools/aapt2/io/Io.cpp
new file mode 100644
index 0000000..cab4b65
--- /dev/null
+++ b/tools/aapt2/io/Io.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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 "io/Io.h"
+
+#include <algorithm>
+#include <cstring>
+
+namespace aapt {
+namespace io {
+
+bool Copy(OutputStream* out, InputStream* in) {
+ const void* in_buffer;
+ int in_len;
+ while (in->Next(&in_buffer, &in_len)) {
+ void* out_buffer;
+ int out_len;
+ if (!out->Next(&out_buffer, &out_len)) {
+ return !out->HadError();
+ }
+
+ const int bytes_to_copy = std::min(in_len, out_len);
+ memcpy(out_buffer, in_buffer, bytes_to_copy);
+ out->BackUp(out_len - bytes_to_copy);
+ in->BackUp(in_len - bytes_to_copy);
+ }
+ return !in->HadError();
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
new file mode 100644
index 0000000..33cdc7b
--- /dev/null
+++ b/tools/aapt2/io/Io.h
@@ -0,0 +1,63 @@
+/*
+ * 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_IO_IO_H
+#define AAPT_IO_IO_H
+
+#include <string>
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+namespace aapt {
+namespace io {
+
+/**
+ * InputStream interface that inherits from protobuf's ZeroCopyInputStream,
+ * but adds error handling methods to better report issues.
+ *
+ * The code style here matches the protobuf style.
+ */
+class InputStream : public ::google::protobuf::io::ZeroCopyInputStream {
+ public:
+ virtual std::string GetError() const { return {}; }
+
+ virtual bool HadError() const = 0;
+};
+
+/**
+ * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream,
+ * but adds error handling methods to better report issues.
+ *
+ * The code style here matches the protobuf style.
+ */
+class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream {
+ public:
+ virtual std::string GetError() const { return {}; }
+
+ virtual bool HadError() const = 0;
+};
+
+/**
+ * Copies the data from in to out. Returns true if there was no error.
+ * If there was an error, check the individual streams' HadError/GetError
+ * methods.
+ */
+bool Copy(OutputStream* out, InputStream* in);
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_IO_H */
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index b3e7a02..f4a128e 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -14,129 +14,128 @@
* limitations under the License.
*/
-#include "Source.h"
#include "io/ZipArchive.h"
-#include "util/Util.h"
-#include <utils/FileMap.h>
-#include <ziparchive/zip_archive.h>
+#include "utils/FileMap.h"
+#include "ziparchive/zip_archive.h"
+
+#include "Source.h"
+#include "util/Util.h"
namespace aapt {
namespace io {
-ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
- mZipHandle(handle), mZipEntry(entry), mSource(source) {
-}
+ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry,
+ const Source& source)
+ : zip_handle_(handle), zip_entry_(entry), source_(source) {}
-std::unique_ptr<IData> ZipFile::openAsData() {
- if (mZipEntry.method == kCompressStored) {
- int fd = GetFileDescriptor(mZipHandle);
+std::unique_ptr<IData> ZipFile::OpenAsData() {
+ if (zip_entry_.method == kCompressStored) {
+ int fd = GetFileDescriptor(zip_handle_);
- android::FileMap fileMap;
- bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
- mZipEntry.uncompressed_length, true);
- if (!result) {
- return {};
- }
- return util::make_unique<MmappedData>(std::move(fileMap));
-
- } else {
- std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
- new uint8_t[mZipEntry.uncompressed_length]);
- int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
- static_cast<uint32_t>(mZipEntry.uncompressed_length));
- if (result != 0) {
- return {};
- }
- return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
+ android::FileMap file_map;
+ bool result = file_map.create(nullptr, fd, zip_entry_.offset,
+ zip_entry_.uncompressed_length, true);
+ if (!result) {
+ return {};
}
-}
+ return util::make_unique<MmappedData>(std::move(file_map));
-const Source& ZipFile::getSource() const {
- return mSource;
-}
-
-ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) :
- mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
-}
-
-bool ZipFileCollectionIterator::hasNext() {
- return mCurrent != mEnd;
-}
-
-IFile* ZipFileCollectionIterator::next() {
- IFile* result = mCurrent->second.get();
- ++mCurrent;
- return result;
-}
-
-ZipFileCollection::ZipFileCollection() : mHandle(nullptr) {
-}
-
-std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path,
- std::string* outError) {
- constexpr static const int32_t kEmptyArchive = -6;
-
- std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
- new ZipFileCollection());
-
- int32_t result = OpenArchive(path.data(), &collection->mHandle);
+ } else {
+ std::unique_ptr<uint8_t[]> data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[zip_entry_.uncompressed_length]);
+ int32_t result =
+ ExtractToMemory(zip_handle_, &zip_entry_, data.get(),
+ static_cast<uint32_t>(zip_entry_.uncompressed_length));
if (result != 0) {
- // If a zip is empty, result will be an error code. This is fine and we should
- // return an empty ZipFileCollection.
- if (result == kEmptyArchive) {
- return collection;
- }
-
- if (outError) *outError = ErrorCodeString(result);
- return {};
+ return {};
}
-
- void* cookie = nullptr;
- result = StartIteration(collection->mHandle, &cookie, nullptr, nullptr);
- if (result != 0) {
- if (outError) *outError = ErrorCodeString(result);
- return {};
- }
-
- using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
- IterationEnder iterationEnder(cookie, EndIteration);
-
- ZipString zipEntryName;
- ZipEntry zipData;
- while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
- std::string zipEntryPath = std::string(reinterpret_cast<const char*>(zipEntryName.name),
- zipEntryName.name_length);
- std::string nestedPath = path.toString() + "@" + zipEntryPath;
- collection->mFiles[zipEntryPath] = util::make_unique<ZipFile>(collection->mHandle,
- zipData,
- Source(nestedPath));
- }
-
- if (result != -1) {
- if (outError) *outError = ErrorCodeString(result);
- return {};
- }
- return collection;
+ return util::make_unique<MallocData>(std::move(data),
+ zip_entry_.uncompressed_length);
+ }
}
-IFile* ZipFileCollection::findFile(const StringPiece& path) {
- auto iter = mFiles.find(path.toString());
- if (iter != mFiles.end()) {
- return iter->second.get();
- }
- return nullptr;
+const Source& ZipFile::GetSource() const { return source_; }
+
+ZipFileCollectionIterator::ZipFileCollectionIterator(
+ ZipFileCollection* collection)
+ : current_(collection->files_.begin()), end_(collection->files_.end()) {}
+
+bool ZipFileCollectionIterator::HasNext() { return current_ != end_; }
+
+IFile* ZipFileCollectionIterator::Next() {
+ IFile* result = current_->second.get();
+ ++current_;
+ return result;
}
-std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() {
- return util::make_unique<ZipFileCollectionIterator>(this);
+ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
+
+std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
+ const StringPiece& path, std::string* out_error) {
+ constexpr static const int32_t kEmptyArchive = -6;
+
+ std::unique_ptr<ZipFileCollection> collection =
+ std::unique_ptr<ZipFileCollection>(new ZipFileCollection());
+
+ int32_t result = OpenArchive(path.data(), &collection->handle_);
+ if (result != 0) {
+ // If a zip is empty, result will be an error code. This is fine and we
+ // should
+ // return an empty ZipFileCollection.
+ if (result == kEmptyArchive) {
+ return collection;
+ }
+
+ if (out_error) *out_error = ErrorCodeString(result);
+ return {};
+ }
+
+ void* cookie = nullptr;
+ result = StartIteration(collection->handle_, &cookie, nullptr, nullptr);
+ if (result != 0) {
+ if (out_error) *out_error = ErrorCodeString(result);
+ return {};
+ }
+
+ using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+ IterationEnder iteration_ender(cookie, EndIteration);
+
+ ZipString zip_entry_name;
+ ZipEntry zip_data;
+ while ((result = Next(cookie, &zip_data, &zip_entry_name)) == 0) {
+ std::string zip_entry_path =
+ std::string(reinterpret_cast<const char*>(zip_entry_name.name),
+ zip_entry_name.name_length);
+ std::string nested_path = path.ToString() + "@" + zip_entry_path;
+ collection->files_[zip_entry_path] = util::make_unique<ZipFile>(
+ collection->handle_, zip_data, Source(nested_path));
+ }
+
+ if (result != -1) {
+ if (out_error) *out_error = ErrorCodeString(result);
+ return {};
+ }
+ return collection;
+}
+
+IFile* ZipFileCollection::FindFile(const StringPiece& path) {
+ auto iter = files_.find(path.ToString());
+ if (iter != files_.end()) {
+ return iter->second.get();
+ }
+ return nullptr;
+}
+
+std::unique_ptr<IFileCollectionIterator> ZipFileCollection::Iterator() {
+ return util::make_unique<ZipFileCollectionIterator>(this);
}
ZipFileCollection::~ZipFileCollection() {
- if (mHandle) {
- CloseArchive(mHandle);
- }
+ if (handle_) {
+ CloseArchive(handle_);
+ }
}
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 565588e..85ca1ae 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -17,67 +17,70 @@
#ifndef AAPT_IO_ZIPARCHIVE_H
#define AAPT_IO_ZIPARCHIVE_H
-#include "io/File.h"
-#include "util/StringPiece.h"
+#include "ziparchive/zip_archive.h"
#include <map>
-#include <ziparchive/zip_archive.h>
+
+#include "io/File.h"
+#include "util/StringPiece.h"
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 zip_handle_;
+ ZipEntry zip_entry_;
+ Source source_;
};
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 current_, end_;
};
/**
* 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 handle_;
+ std::map<std::string, std::unique_ptr<IFile>> files_;
};
-} // namespace io
-} // namespace aapt
+} // namespace io
+} // namespace aapt
#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 23ff8ab..2951e5c 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -15,69 +15,71 @@
*/
#include "java/AnnotationProcessor.h"
-#include "util/Util.h"
#include <algorithm>
+#include "util/Util.h"
+
namespace aapt {
-void AnnotationProcessor::appendCommentLine(std::string& comment) {
- static const std::string sDeprecated = "@deprecated";
- static const std::string sSystemApi = "@SystemApi";
+void AnnotationProcessor::AppendCommentLine(std::string& comment) {
+ static const std::string sDeprecated = "@deprecated";
+ static const std::string sSystemApi = "@SystemApi";
- if (comment.find(sDeprecated) != std::string::npos) {
- mAnnotationBitMask |= kDeprecated;
- }
+ if (comment.find(sDeprecated) != std::string::npos) {
+ annotation_bit_mask_ |= kDeprecated;
+ }
- std::string::size_type idx = comment.find(sSystemApi);
- if (idx != std::string::npos) {
- mAnnotationBitMask |= kSystemApi;
- comment.erase(comment.begin() + idx, comment.begin() + idx + sSystemApi.size());
- }
+ std::string::size_type idx = comment.find(sSystemApi);
+ if (idx != std::string::npos) {
+ annotation_bit_mask_ |= kSystemApi;
+ comment.erase(comment.begin() + idx,
+ comment.begin() + idx + sSystemApi.size());
+ }
- if (util::trimWhitespace(comment).empty()) {
- return;
- }
+ if (util::TrimWhitespace(comment).empty()) {
+ return;
+ }
- if (!mHasComments) {
- mHasComments = true;
- mComment << "/**";
- }
+ if (!has_comments_) {
+ has_comments_ = true;
+ comment_ << "/**";
+ }
- mComment << "\n * " << std::move(comment);
+ comment_ << "\n * " << std::move(comment);
}
-void AnnotationProcessor::appendComment(const StringPiece& comment) {
- // We need to process line by line to clean-up whitespace and append prefixes.
- for (StringPiece line : util::tokenize(comment, '\n')) {
- line = util::trimWhitespace(line);
- if (!line.empty()) {
- std::string lineCopy = line.toString();
- appendCommentLine(lineCopy);
- }
+void AnnotationProcessor::AppendComment(const StringPiece& comment) {
+ // We need to process line by line to clean-up whitespace and append prefixes.
+ for (StringPiece line : util::Tokenize(comment, '\n')) {
+ line = util::TrimWhitespace(line);
+ if (!line.empty()) {
+ std::string lineCopy = line.ToString();
+ AppendCommentLine(lineCopy);
}
+ }
}
-void AnnotationProcessor::appendNewLine() {
- mComment << "\n *";
+void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; }
+
+void AnnotationProcessor::WriteToStream(std::ostream* out,
+ const StringPiece& prefix) const {
+ if (has_comments_) {
+ std::string result = comment_.str();
+ for (StringPiece line : util::Tokenize(result, '\n')) {
+ *out << prefix << line << "\n";
+ }
+ *out << prefix << " */"
+ << "\n";
+ }
+
+ if (annotation_bit_mask_ & kDeprecated) {
+ *out << prefix << "@Deprecated\n";
+ }
+
+ if (annotation_bit_mask_ & kSystemApi) {
+ *out << prefix << "@android.annotation.SystemApi\n";
+ }
}
-void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) const {
- if (mHasComments) {
- std::string result = mComment.str();
- for (StringPiece line : util::tokenize(result, '\n')) {
- *out << prefix << line << "\n";
- }
- *out << prefix << " */" << "\n";
- }
-
- if (mAnnotationBitMask & kDeprecated) {
- *out << prefix << "@Deprecated\n";
- }
-
- if (mAnnotationBitMask & kSystemApi) {
- *out << prefix << "@android.annotation.SystemApi\n";
- }
-}
-
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index cfc32f3..666a7f3 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -17,11 +17,11 @@
#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
-#include "util/StringPiece.h"
-
#include <sstream>
#include <string>
+#include "util/StringPiece.h"
+
namespace aapt {
/**
@@ -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 comment_;
+ std::stringstream mAnnotations;
+ bool has_comments_ = false;
+ uint32_t annotation_bit_mask_ = 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/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 5a39add..3e43c42 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -15,38 +15,39 @@
*/
#include "java/AnnotationProcessor.h"
+
#include "test/Test.h"
namespace aapt {
TEST(AnnotationProcessorTest, EmitsDeprecated) {
- const char* comment = "Some comment, and it should contain a marker word, "
- "something that marks this resource as nor needed. "
- "{@deprecated That's the marker! }";
+ const char* comment =
+ "Some comment, and it should contain a marker word, "
+ "something that marks this resource as nor needed. "
+ "{@deprecated That's the marker! }";
- AnnotationProcessor processor;
- processor.appendComment(comment);
+ AnnotationProcessor processor;
+ processor.AppendComment(comment);
- std::stringstream result;
- processor.writeToStream(&result, "");
- std::string annotations = result.str();
+ std::stringstream result;
+ processor.WriteToStream(&result, "");
+ std::string annotations = result.str();
- EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
+ EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
}
TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) {
- AnnotationProcessor processor;
- processor.appendComment("@SystemApi This is a system API");
+ AnnotationProcessor processor;
+ processor.AppendComment("@SystemApi This is a system API");
- std::stringstream result;
- processor.writeToStream(&result, "");
- std::string annotations = result.str();
+ std::stringstream result;
+ processor.WriteToStream(&result, "");
+ std::string annotations = result.str();
- EXPECT_NE(std::string::npos, annotations.find("@android.annotation.SystemApi"));
- EXPECT_EQ(std::string::npos, annotations.find("@SystemApi"));
- EXPECT_NE(std::string::npos, annotations.find("This is a system API"));
+ EXPECT_NE(std::string::npos,
+ annotations.find("@android.annotation.SystemApi"));
+ EXPECT_EQ(std::string::npos, annotations.find("@SystemApi"));
+ EXPECT_NE(std::string::npos, annotations.find("This is a system API"));
}
-} // namespace aapt
-
-
+} // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 08f2c8b..f1f1f92 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -15,61 +15,59 @@
*/
#include "java/ClassDefinition.h"
-#include "util/StringPiece.h"
-#include <ostream>
+#include "util/StringPiece.h"
namespace aapt {
bool ClassDefinition::empty() const {
- for (const std::unique_ptr<ClassMember>& member : mMembers) {
- if (!member->empty()) {
- return false;
- }
+ for (const std::unique_ptr<ClassMember>& member : members_) {
+ if (!member->empty()) {
+ return false;
}
- return true;
+ }
+ return true;
}
-void ClassDefinition::writeToStream(const StringPiece& prefix, bool final,
+void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final,
std::ostream* out) const {
- if (mMembers.empty() && !mCreateIfEmpty) {
- return;
- }
+ if (members_.empty() && !create_if_empty_) {
+ return;
+ }
- ClassMember::writeToStream(prefix, final, out);
+ ClassMember::WriteToStream(prefix, final, out);
- *out << prefix << "public ";
- if (mQualifier == ClassQualifier::Static) {
- *out << "static ";
- }
- *out << "final class " << mName << " {\n";
+ *out << prefix << "public ";
+ if (qualifier_ == ClassQualifier::Static) {
+ *out << "static ";
+ }
+ *out << "final class " << name_ << " {\n";
- std::string newPrefix = prefix.toString();
- newPrefix.append(kIndent);
+ std::string new_prefix = prefix.ToString();
+ new_prefix.append(kIndent);
- for (const std::unique_ptr<ClassMember>& member : mMembers) {
- member->writeToStream(newPrefix, final, out);
- *out << "\n";
- }
+ for (const std::unique_ptr<ClassMember>& member : members_) {
+ member->WriteToStream(new_prefix, final, out);
+ *out << "\n";
+ }
- *out << prefix << "}";
+ *out << prefix << "}";
}
constexpr static const char* sWarningHeader =
- "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
- " *\n"
- " * This class was automatically generated by the\n"
- " * aapt tool from the resource data it found. It\n"
- " * should not be modified by hand.\n"
- " */\n\n";
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ " *\n"
+ " * This class was automatically generated by the\n"
+ " * aapt tool from the resource data it found. It\n"
+ " * should not be modified by hand.\n"
+ " */\n\n";
-bool ClassDefinition::writeJavaFile(const ClassDefinition* def,
- const StringPiece& package,
- bool final,
+bool ClassDefinition::WriteJavaFile(const ClassDefinition* def,
+ const StringPiece& package, bool final,
std::ostream* out) {
- *out << sWarningHeader << "package " << package << ";\n\n";
- def->writeToStream("", final, out);
- return bool(*out);
+ *out << sWarningHeader << "package " << package << ";\n\n";
+ def->WriteToStream("", final, out);
+ return bool(*out);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index e84c274..d8b61d9 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -17,15 +17,16 @@
#ifndef AAPT_JAVA_CLASSDEFINITION_H
#define AAPT_JAVA_CLASSDEFINITION_H
+#include <ostream>
+#include <string>
+
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "java/AnnotationProcessor.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-#include <android-base/macros.h>
-#include <sstream>
-#include <string>
-
namespace aapt {
// The number of attributes to emit per line in a Styleable array.
@@ -33,46 +34,43 @@
constexpr static const char* kIndent = " ";
class ClassMember {
-public:
- virtual ~ClassMember() = default;
+ public:
+ virtual ~ClassMember() = default;
- AnnotationProcessor* getCommentBuilder() {
- return &mProcessor;
- }
+ AnnotationProcessor* GetCommentBuilder() { return &processor_; }
- 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 {
+ processor_.WriteToStream(out, prefix);
+ }
-private:
- AnnotationProcessor mProcessor;
+ private:
+ AnnotationProcessor processor_;
};
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)
+ : name_(name.ToString()), val_(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 "
+ << name_ << "=" << val_ << ";";
+ }
-private:
- std::string mName;
- T mVal;
+ private:
+ std::string name_;
+ T val_;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
/**
@@ -80,27 +78,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)
+ : name_(name.ToString()), val_(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 "
+ << name_ << "=\"" << val_ << "\";";
+ }
-private:
- std::string mName;
- std::string mVal;
+ private:
+ std::string name_;
+ std::string val_;
- DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+ DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
};
using IntMember = PrimitiveMember<uint32_t>;
@@ -109,80 +105,75 @@
template <typename T>
class PrimitiveArrayMember : public ClassMember {
-public:
- explicit PrimitiveArrayMember(const StringPiece& name) :
- mName(name.toString()) {
+ public:
+ explicit PrimitiveArrayMember(const StringPiece& name)
+ : name_(name.ToString()) {}
+
+ void AddElement(const T& val) { elements_.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[] " << name_ << "={";
+
+ const auto begin = elements_.begin();
+ const auto end = elements_.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 name_;
+ std::vector<T> elements_;
- 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)
+ : name_(name.ToString()),
+ qualifier_(qualifier),
+ create_if_empty_(createIfEmpty) {}
- void addMember(std::unique_ptr<ClassMember> member) {
- mMembers.push_back(std::move(member));
- }
+ void AddMember(std::unique_ptr<ClassMember> member) {
+ members_.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 name_;
+ ClassQualifier qualifier_;
+ bool create_if_empty_;
+ std::vector<std::unique_ptr<ClassMember>> members_;
- 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.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index fbaefb1..6e7c7078 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -14,16 +14,7 @@
* limitations under the License.
*/
-#include "NameMangler.h"
-#include "Resource.h"
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "ValueVisitor.h"
-#include "java/AnnotationProcessor.h"
-#include "java/ClassDefinition.h"
#include "java/JavaClassGenerator.h"
-#include "process/SymbolTable.h"
-#include "util/StringPiece.h"
#include <algorithm>
#include <ostream>
@@ -31,42 +22,49 @@
#include <sstream>
#include <tuple>
+#include "android-base/logging.h"
+
+#include "NameMangler.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "java/AnnotationProcessor.h"
+#include "java/ClassDefinition.h"
+#include "process/SymbolTable.h"
+#include "util/StringPiece.h"
+
namespace aapt {
-JavaClassGenerator::JavaClassGenerator(IAaptContext* context, ResourceTable* table,
- const JavaClassGeneratorOptions& options) :
- mContext(context), mTable(table), mOptions(options) {
-}
-
static const std::set<StringPiece> sJavaIdentifiers = {
- "abstract", "assert", "boolean", "break", "byte",
- "case", "catch", "char", "class", "const", "continue",
- "default", "do", "double", "else", "enum", "extends",
- "final", "finally", "float", "for", "goto", "if",
- "implements", "import", "instanceof", "int", "interface",
- "long", "native", "new", "package", "private", "protected",
- "public", "return", "short", "static", "strictfp", "super",
- "switch", "synchronized", "this", "throw", "throws",
- "transient", "try", "void", "volatile", "while", "true",
- "false", "null"
-};
+ "abstract", "assert", "boolean", "break", "byte",
+ "case", "catch", "char", "class", "const",
+ "continue", "default", "do", "double", "else",
+ "enum", "extends", "final", "finally", "float",
+ "for", "goto", "if", "implements", "import",
+ "instanceof", "int", "interface", "long", "native",
+ "new", "package", "private", "protected", "public",
+ "return", "short", "static", "strictfp", "super",
+ "switch", "synchronized", "this", "throw", "throws",
+ "transient", "try", "void", "volatile", "while",
+ "true", "false", "null"};
-static bool isValidSymbol(const StringPiece& symbol) {
- return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
+static bool IsValidSymbol(const StringPiece& symbol) {
+ return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
}
/*
* Java symbols can not contain . or -, but those are valid in a resource name.
* Replace those with '_'.
*/
-static std::string transform(const StringPiece& symbol) {
- std::string output = symbol.toString();
- for (char& c : output) {
- if (c == '.' || c == '-') {
- c = '_';
- }
+static std::string Transform(const StringPiece& symbol) {
+ std::string output = symbol.ToString();
+ for (char& c : output) {
+ if (c == '.' || c == '-') {
+ c = '_';
}
- return output;
+ }
+ return output;
}
/**
@@ -80,475 +78,519 @@
* Foo_android_bar
* Foo_bar
*/
-static std::string transformNestedAttr(const ResourceNameRef& attrName,
- const std::string& styleableClassName,
- const StringPiece& packageNameToGenerate) {
- std::string output = styleableClassName;
+static std::string TransformNestedAttr(
+ const ResourceNameRef& attr_name, const std::string& styleable_class_name,
+ const StringPiece& package_name_to_generate) {
+ std::string output = styleable_class_name;
- // We may reference IDs from other packages, so prefix the entry name with
- // the package.
- if (!attrName.package.empty() && packageNameToGenerate != attrName.package) {
- output += "_" + transform(attrName.package);
- }
- output += "_" + transform(attrName.entry);
- return output;
+ // We may reference IDs from other packages, so prefix the entry name with
+ // the package.
+ if (!attr_name.package.empty() &&
+ package_name_to_generate != attr_name.package) {
+ output += "_" + Transform(attr_name.package);
+ }
+ output += "_" + Transform(attr_name.entry);
+ return output;
}
-static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
- const uint32_t typeMask = attr->typeMask;
- if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
- processor->appendComment(
- "<p>May be a reference to another resource, in the form\n"
- "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
- "attribute in the form\n"
- "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
+static void AddAttributeFormatDoc(AnnotationProcessor* processor,
+ Attribute* attr) {
+ const uint32_t type_mask = attr->type_mask;
+ if (type_mask & android::ResTable_map::TYPE_REFERENCE) {
+ processor->AppendComment(
+ "<p>May be a reference to another resource, in the form\n"
+ "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a "
+ "theme\n"
+ "attribute in the form\n"
+ "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_STRING) {
+ processor->AppendComment(
+ "<p>May be a string value, using '\\\\;' to escape characters such as\n"
+ "'\\\\n' or '\\\\uxxxx' for a unicode character;");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_INTEGER) {
+ processor->AppendComment(
+ "<p>May be an integer value, such as \"<code>100</code>\".");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
+ processor->AppendComment(
+ "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
+ "\"<code>false</code>\".");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_COLOR) {
+ processor->AppendComment(
+ "<p>May be a color value, in the form of "
+ "\"<code>#<i>rgb</i></code>\",\n"
+ "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
+ "\"<code>#<i>aarrggbb</i></code>\".");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_FLOAT) {
+ processor->AppendComment(
+ "<p>May be a floating point value, such as \"<code>1.2</code>\".");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_DIMENSION) {
+ processor->AppendComment(
+ "<p>May be a dimension value, which is a floating point number "
+ "appended with a\n"
+ "unit such as \"<code>14.5sp</code>\".\n"
+ "Available units are: px (pixels), dp (density-independent pixels),\n"
+ "sp (scaled pixels based on preferred font size), in (inches), and\n"
+ "mm (millimeters).");
+ }
+
+ if (type_mask & android::ResTable_map::TYPE_FRACTION) {
+ processor->AppendComment(
+ "<p>May be a fractional value, which is a floating point number "
+ "appended with\n"
+ "either % or %p, such as \"<code>14.5%</code>\".\n"
+ "The % suffix always means a percentage of the base size;\n"
+ "the optional %p suffix provides a size relative to some parent "
+ "container.");
+ }
+
+ if (type_mask &
+ (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
+ if (type_mask & android::ResTable_map::TYPE_FLAGS) {
+ processor->AppendComment(
+ "<p>Must be one or more (separated by '|') of the following "
+ "constant values.</p>");
+ } else {
+ processor->AppendComment(
+ "<p>Must be one of the following constant values.</p>");
}
- if (typeMask & android::ResTable_map::TYPE_STRING) {
- processor->appendComment(
- "<p>May be a string value, using '\\\\;' to escape characters such as\n"
- "'\\\\n' or '\\\\uxxxx' for a unicode character;");
+ processor->AppendComment(
+ "<table>\n<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
+ for (const Attribute::Symbol& symbol : attr->symbols) {
+ std::stringstream line;
+ line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
+ << "<td>" << std::hex << symbol.value << std::dec << "</td>"
+ << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
+ << "</td></tr>";
+ processor->AppendComment(line.str());
}
-
- if (typeMask & android::ResTable_map::TYPE_INTEGER) {
- processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
- }
-
- if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- processor->appendComment(
- "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
- "\"<code>false</code>\".");
- }
-
- if (typeMask & android::ResTable_map::TYPE_COLOR) {
- processor->appendComment(
- "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
- "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
- "\"<code>#<i>aarrggbb</i></code>\".");
- }
-
- if (typeMask & android::ResTable_map::TYPE_FLOAT) {
- processor->appendComment(
- "<p>May be a floating point value, such as \"<code>1.2</code>\".");
- }
-
- if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
- processor->appendComment(
- "<p>May be a dimension value, which is a floating point number appended with a\n"
- "unit such as \"<code>14.5sp</code>\".\n"
- "Available units are: px (pixels), dp (density-independent pixels),\n"
- "sp (scaled pixels based on preferred font size), in (inches), and\n"
- "mm (millimeters).");
- }
-
- if (typeMask & android::ResTable_map::TYPE_FRACTION) {
- processor->appendComment(
- "<p>May be a fractional value, which is a floating point number appended with\n"
- "either % or %p, such as \"<code>14.5%</code>\".\n"
- "The % suffix always means a percentage of the base size;\n"
- "the optional %p suffix provides a size relative to some parent container.");
- }
-
- if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
- if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- processor->appendComment(
- "<p>Must be one or more (separated by '|') of the following "
- "constant values.</p>");
- } else {
- processor->appendComment("<p>Must be one of the following constant values.</p>");
- }
-
- processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
- "<colgroup align=\"left\" />\n"
- "<colgroup align=\"left\" />\n"
- "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
- for (const Attribute::Symbol& symbol : attr->symbols) {
- std::stringstream line;
- line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
- << "<td>" << std::hex << symbol.value << std::dec << "</td>"
- << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
- processor->appendComment(line.str());
- }
- processor->appendComment("</table>");
- }
+ processor->AppendComment("</table>");
+ }
}
-bool JavaClassGenerator::skipSymbol(SymbolState state) {
- switch (mOptions.types) {
+JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
+ ResourceTable* table,
+ const JavaClassGeneratorOptions& options)
+ : context_(context), table_(table), options_(options) {}
+
+bool JavaClassGenerator::SkipSymbol(SymbolState state) {
+ switch (options_.types) {
case JavaClassGeneratorOptions::SymbolTypes::kAll:
- return false;
+ return false;
case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
- return state == SymbolState::kUndefined;
+ return state == SymbolState::kUndefined;
case JavaClassGeneratorOptions::SymbolTypes::kPublic:
- return state != SymbolState::kPublic;
- }
- return true;
+ return state != SymbolState::kPublic;
+ }
+ return true;
}
struct StyleableAttr {
- const Reference* attrRef;
- std::string fieldName;
- std::unique_ptr<SymbolTable::Symbol> symbol;
+ const Reference* attr_ref;
+ std::string field_name;
+ std::unique_ptr<SymbolTable::Symbol> symbol;
};
-static bool lessStyleableAttr(const StyleableAttr& lhs, const StyleableAttr& rhs) {
- const ResourceId lhsId = lhs.attrRef->id ? lhs.attrRef->id.value() : ResourceId(0);
- const ResourceId rhsId = rhs.attrRef->id ? rhs.attrRef->id.value() : ResourceId(0);
- if (lhsId < rhsId) {
- return true;
- } else if (lhsId > rhsId) {
- return false;
+static bool less_styleable_attr(const StyleableAttr& lhs,
+ const StyleableAttr& rhs) {
+ const ResourceId lhs_id =
+ lhs.attr_ref->id ? lhs.attr_ref->id.value() : ResourceId(0);
+ const ResourceId rhs_id =
+ rhs.attr_ref->id ? rhs.attr_ref->id.value() : ResourceId(0);
+ if (lhs_id < rhs_id) {
+ return true;
+ } else if (lhs_id > rhs_id) {
+ return false;
+ } else {
+ return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
+ }
+}
+
+void JavaClassGenerator::AddMembersToStyleableClass(
+ const StringPiece& package_name_to_generate, const std::string& entry_name,
+ const Styleable* styleable, ClassDefinition* out_styleable_class_def) {
+ const std::string class_name = Transform(entry_name);
+
+ std::unique_ptr<ResourceArrayMember> styleable_array_def =
+ util::make_unique<ResourceArrayMember>(class_name);
+
+ // This must be sorted by resource ID.
+ std::vector<StyleableAttr> sorted_attributes;
+ sorted_attributes.reserve(styleable->entries.size());
+ for (const auto& attr : styleable->entries) {
+ // If we are not encoding final attributes, the styleable entry may have no
+ // ID if we are building a static library.
+ CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
+ CHECK(bool(attr.name)) << "no name set for Styleable entry";
+
+ // We will need the unmangled, transformed name in the comments and the
+ // field,
+ // so create it once and cache it in this StyleableAttr data structure.
+ StyleableAttr styleable_attr = {};
+ styleable_attr.attr_ref = &attr;
+ styleable_attr.field_name = TransformNestedAttr(
+ attr.name.value(), class_name, package_name_to_generate);
+
+ Reference mangled_reference;
+ mangled_reference.id = attr.id;
+ mangled_reference.name = attr.name;
+ if (mangled_reference.name.value().package.empty()) {
+ mangled_reference.name.value().package =
+ context_->GetCompilationPackage();
+ }
+
+ if (Maybe<ResourceName> mangled_name =
+ context_->GetNameMangler()->MangleName(
+ mangled_reference.name.value())) {
+ mangled_reference.name = mangled_name;
+ }
+
+ // Look up the symbol so that we can write out in the comments what are
+ // possible
+ // legal values for this attribute.
+ const SymbolTable::Symbol* symbol =
+ context_->GetExternalSymbols()->FindByReference(mangled_reference);
+ if (symbol && symbol->attribute) {
+ // Copy the symbol data structure because the returned instance can be
+ // destroyed.
+ styleable_attr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol);
+ }
+ sorted_attributes.push_back(std::move(styleable_attr));
+ }
+
+ // Sort the attributes by ID.
+ std::sort(sorted_attributes.begin(), sorted_attributes.end(),
+ less_styleable_attr);
+
+ const size_t attr_count = sorted_attributes.size();
+ if (attr_count > 0) {
+ // Build the comment string for the Styleable. It includes details about the
+ // child attributes.
+ std::stringstream styleable_comment;
+ if (!styleable->GetComment().empty()) {
+ styleable_comment << styleable->GetComment() << "\n";
} else {
- return lhs.attrRef->name.value() < rhs.attrRef->name.value();
+ styleable_comment << "Attributes that can be used with a " << class_name
+ << ".\n";
}
+
+ styleable_comment << "<p>Includes the following attributes:</p>\n"
+ "<table>\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Attribute</th><th>Description</th></tr>\n";
+
+ for (const StyleableAttr& entry : sorted_attributes) {
+ if (!entry.symbol) {
+ continue;
+ }
+
+ if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !entry.symbol->is_public) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+
+ StringPiece attr_comment_line = entry.symbol->attribute->GetComment();
+ if (attr_comment_line.contains("@removed")) {
+ // Removed attributes are public but hidden from the documentation, so
+ // don't emit
+ // them as part of the class documentation.
+ continue;
+ }
+
+ const ResourceName& attr_name = entry.attr_ref->name.value();
+ styleable_comment << "<tr><td>";
+ styleable_comment << "<code>{@link #" << entry.field_name << " "
+ << (!attr_name.package.empty()
+ ? attr_name.package
+ : context_->GetCompilationPackage())
+ << ":" << attr_name.entry << "}</code>";
+ styleable_comment << "</td>";
+
+ styleable_comment << "<td>";
+
+ // Only use the comment up until the first '.'. This is to stay compatible
+ // with
+ // the way old AAPT did it (presumably to keep it short and to avoid
+ // including
+ // annotations like @hide which would affect this Styleable).
+ auto iter =
+ std::find(attr_comment_line.begin(), attr_comment_line.end(), u'.');
+ if (iter != attr_comment_line.end()) {
+ attr_comment_line =
+ attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
+ }
+ styleable_comment << attr_comment_line << "</td></tr>\n";
+ }
+ styleable_comment << "</table>\n";
+
+ for (const StyleableAttr& entry : sorted_attributes) {
+ if (!entry.symbol) {
+ continue;
+ }
+
+ if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !entry.symbol->is_public) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+ styleable_comment << "@see #" << entry.field_name << "\n";
+ }
+
+ styleable_array_def->GetCommentBuilder()->AppendComment(
+ styleable_comment.str());
+ }
+
+ // Add the ResourceIds to the array member.
+ for (const StyleableAttr& styleable_attr : sorted_attributes) {
+ styleable_array_def->AddElement(styleable_attr.attr_ref->id
+ ? styleable_attr.attr_ref->id.value()
+ : ResourceId(0));
+ }
+
+ // Add the Styleable array to the Styleable class.
+ out_styleable_class_def->AddMember(std::move(styleable_array_def));
+
+ // Now we emit the indices into the array.
+ for (size_t i = 0; i < attr_count; i++) {
+ const StyleableAttr& styleable_attr = sorted_attributes[i];
+
+ if (!styleable_attr.symbol) {
+ continue;
+ }
+
+ if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+ !styleable_attr.symbol->is_public) {
+ // Don't write entries for non-public attributes.
+ continue;
+ }
+
+ StringPiece comment = styleable_attr.attr_ref->GetComment();
+ if (styleable_attr.symbol->attribute && comment.empty()) {
+ comment = styleable_attr.symbol->attribute->GetComment();
+ }
+
+ if (comment.contains("@removed")) {
+ // Removed attributes are public but hidden from the documentation, so
+ // don't emit them
+ // as part of the class documentation.
+ continue;
+ }
+
+ const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
+
+ StringPiece package_name = attr_name.package;
+ if (package_name.empty()) {
+ package_name = context_->GetCompilationPackage();
+ }
+
+ std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>(
+ sorted_attributes[i].field_name, static_cast<uint32_t>(i));
+
+ AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
+
+ if (!comment.empty()) {
+ attr_processor->AppendComment("<p>\n@attr description");
+ attr_processor->AppendComment(comment);
+ } else {
+ std::stringstream default_comment;
+ default_comment << "<p>This symbol is the offset where the "
+ << "{@link " << package_name << ".R.attr#"
+ << Transform(attr_name.entry) << "}\n"
+ << "attribute's value can be found in the "
+ << "{@link #" << class_name << "} array.";
+ attr_processor->AppendComment(default_comment.str());
+ }
+
+ attr_processor->AppendNewLine();
+
+ AddAttributeFormatDoc(attr_processor,
+ styleable_attr.symbol->attribute.get());
+ attr_processor->AppendNewLine();
+
+ std::stringstream doclava_name;
+ doclava_name << "@attr name " << package_name << ":" << attr_name.entry;
+
+ attr_processor->AppendComment(doclava_name.str());
+
+ out_styleable_class_def->AddMember(std::move(index_member));
+ }
}
-void JavaClassGenerator::addMembersToStyleableClass(const StringPiece& packageNameToGenerate,
- const std::string& entryName,
- const Styleable* styleable,
- ClassDefinition* outStyleableClassDef) {
- const std::string className = transform(entryName);
-
- std::unique_ptr<ResourceArrayMember> styleableArrayDef =
- util::make_unique<ResourceArrayMember>(className);
-
- // This must be sorted by resource ID.
- std::vector<StyleableAttr> sortedAttributes;
- sortedAttributes.reserve(styleable->entries.size());
- for (const auto& attr : styleable->entries) {
- // If we are not encoding final attributes, the styleable entry may have no ID
- // if we are building a static library.
- assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
- assert(attr.name && "no name set for Styleable entry");
-
- // We will need the unmangled, transformed name in the comments and the field,
- // so create it once and cache it in this StyleableAttr data structure.
- StyleableAttr styleableAttr = {};
- styleableAttr.attrRef = &attr;
- styleableAttr.fieldName = transformNestedAttr(attr.name.value(), className,
- packageNameToGenerate);
-
- Reference mangledReference;
- mangledReference.id = attr.id;
- mangledReference.name = attr.name;
- if (mangledReference.name.value().package.empty()) {
- mangledReference.name.value().package = mContext->getCompilationPackage();
- }
-
- if (Maybe<ResourceName> mangledName =
- mContext->getNameMangler()->mangleName(mangledReference.name.value())) {
- mangledReference.name = mangledName;
- }
-
- // Look up the symbol so that we can write out in the comments what are possible
- // legal values for this attribute.
- const SymbolTable::Symbol* symbol = mContext->getExternalSymbols()->findByReference(
- mangledReference);
- if (symbol && symbol->attribute) {
- // Copy the symbol data structure because the returned instance can be destroyed.
- styleableAttr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol);
- }
- sortedAttributes.push_back(std::move(styleableAttr));
+bool JavaClassGenerator::AddMembersToTypeClass(
+ const StringPiece& package_name_to_generate,
+ const ResourceTablePackage* package, const ResourceTableType* type,
+ ClassDefinition* out_type_class_def) {
+ for (const auto& entry : type->entries) {
+ if (SkipSymbol(entry->symbol_status.state)) {
+ continue;
}
- // Sort the attributes by ID.
- std::sort(sortedAttributes.begin(), sortedAttributes.end(), lessStyleableAttr);
-
- const size_t attrCount = sortedAttributes.size();
- if (attrCount > 0) {
- // Build the comment string for the Styleable. It includes details about the
- // child attributes.
- std::stringstream styleableComment;
- if (!styleable->getComment().empty()) {
- styleableComment << styleable->getComment() << "\n";
- } else {
- styleableComment << "Attributes that can be used with a " << className << ".\n";
- }
-
- styleableComment <<
- "<p>Includes the following attributes:</p>\n"
- "<table>\n"
- "<colgroup align=\"left\" />\n"
- "<colgroup align=\"left\" />\n"
- "<tr><th>Attribute</th><th>Description</th></tr>\n";
-
- for (const StyleableAttr& entry : sortedAttributes) {
- if (!entry.symbol) {
- continue;
- }
-
- if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
- !entry.symbol->isPublic) {
- // Don't write entries for non-public attributes.
- continue;
- }
-
- StringPiece attrCommentLine = entry.symbol->attribute->getComment();
- if (attrCommentLine.contains("@removed")) {
- // Removed attributes are public but hidden from the documentation, so don't emit
- // them as part of the class documentation.
- continue;
- }
-
- const ResourceName& attrName = entry.attrRef->name.value();
- styleableComment << "<tr><td>";
- styleableComment << "<code>{@link #"
- << entry.fieldName << " "
- << (!attrName.package.empty()
- ? attrName.package : mContext->getCompilationPackage())
- << ":" << attrName.entry
- << "}</code>";
- styleableComment << "</td>";
-
- styleableComment << "<td>";
-
- // Only use the comment up until the first '.'. This is to stay compatible with
- // the way old AAPT did it (presumably to keep it short and to avoid including
- // annotations like @hide which would affect this Styleable).
- auto iter = std::find(attrCommentLine.begin(), attrCommentLine.end(), u'.');
- if (iter != attrCommentLine.end()) {
- attrCommentLine = attrCommentLine.substr(
- 0, (iter - attrCommentLine.begin()) + 1);
- }
- styleableComment << attrCommentLine << "</td></tr>\n";
- }
- styleableComment << "</table>\n";
-
- for (const StyleableAttr& entry : sortedAttributes) {
- if (!entry.symbol) {
- continue;
- }
-
- if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
- !entry.symbol->isPublic) {
- // Don't write entries for non-public attributes.
- continue;
- }
- styleableComment << "@see #" << entry.fieldName << "\n";
- }
-
- styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str());
+ ResourceId id;
+ if (package->id && type->id && entry->id) {
+ id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
}
- // Add the ResourceIds to the array member.
- for (const StyleableAttr& styleableAttr : sortedAttributes) {
- styleableArrayDef->addElement(
- styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0));
+ std::string unmangled_package;
+ std::string unmangled_name = entry->name;
+ if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package->name != unmangled_package) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else if (package_name_to_generate != package->name) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
}
- // Add the Styleable array to the Styleable class.
- outStyleableClassDef->addMember(std::move(styleableArrayDef));
-
- // Now we emit the indices into the array.
- for (size_t i = 0; i < attrCount; i++) {
- const StyleableAttr& styleableAttr = sortedAttributes[i];
-
- if (!styleableAttr.symbol) {
- continue;
- }
-
- if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
- !styleableAttr.symbol->isPublic) {
- // Don't write entries for non-public attributes.
- continue;
- }
-
- StringPiece comment = styleableAttr.attrRef->getComment();
- if (styleableAttr.symbol->attribute && comment.empty()) {
- comment = styleableAttr.symbol->attribute->getComment();
- }
-
- if (comment.contains("@removed")) {
- // Removed attributes are public but hidden from the documentation, so don't emit them
- // as part of the class documentation.
- continue;
- }
-
- const ResourceName& attrName = styleableAttr.attrRef->name.value();
-
- StringPiece packageName = attrName.package;
- if (packageName.empty()) {
- packageName = mContext->getCompilationPackage();
- }
-
- std::unique_ptr<IntMember> indexMember = util::make_unique<IntMember>(
- sortedAttributes[i].fieldName, static_cast<uint32_t>(i));
-
- AnnotationProcessor* attrProcessor = indexMember->getCommentBuilder();
-
- if (!comment.empty()) {
- attrProcessor->appendComment("<p>\n@attr description");
- attrProcessor->appendComment(comment);
- } else {
- std::stringstream defaultComment;
- defaultComment
- << "<p>This symbol is the offset where the "
- << "{@link " << packageName << ".R.attr#" << transform(attrName.entry) << "}\n"
- << "attribute's value can be found in the "
- << "{@link #" << className << "} array.";
- attrProcessor->appendComment(defaultComment.str());
- }
-
- attrProcessor->appendNewLine();
-
- addAttributeFormatDoc(attrProcessor, styleableAttr.symbol->attribute.get());
- attrProcessor->appendNewLine();
-
- std::stringstream doclavaName;
- doclavaName << "@attr name " << packageName << ":" << attrName.entry;;
- attrProcessor->appendComment(doclavaName.str());
-
- outStyleableClassDef->addMember(std::move(indexMember));
+ if (!IsValidSymbol(unmangled_name)) {
+ ResourceNameRef resource_name(package_name_to_generate, type->type,
+ unmangled_name);
+ std::stringstream err;
+ err << "invalid symbol name '" << resource_name << "'";
+ error_ = err.str();
+ return false;
}
+
+ if (type->type == ResourceType::kStyleable) {
+ CHECK(!entry->values.empty());
+
+ const Styleable* styleable =
+ static_cast<const Styleable*>(entry->values.front()->value.get());
+
+ // Comments are handled within this method.
+ AddMembersToStyleableClass(package_name_to_generate, unmangled_name,
+ styleable, out_type_class_def);
+ } else {
+ std::unique_ptr<ResourceMember> resource_member =
+ util::make_unique<ResourceMember>(Transform(unmangled_name), id);
+
+ // Build the comments and annotations for this entry.
+ AnnotationProcessor* processor = resource_member->GetCommentBuilder();
+
+ // Add the comments from any <public> tags.
+ if (entry->symbol_status.state != SymbolState::kUndefined) {
+ processor->AppendComment(entry->symbol_status.comment);
+ }
+
+ // Add the comments from all configurations of this entry.
+ for (const auto& config_value : entry->values) {
+ processor->AppendComment(config_value->value->GetComment());
+ }
+
+ // If this is an Attribute, append the format Javadoc.
+ if (!entry->values.empty()) {
+ if (Attribute* attr =
+ ValueCast<Attribute>(entry->values.front()->value.get())) {
+ // We list out the available values for the given attribute.
+ AddAttributeFormatDoc(processor, attr);
+ }
+ }
+
+ out_type_class_def->AddMember(std::move(resource_member));
+ }
+ }
+ return true;
}
-bool JavaClassGenerator::addMembersToTypeClass(const StringPiece& packageNameToGenerate,
- const ResourceTablePackage* package,
- const ResourceTableType* type,
- ClassDefinition* outTypeClassDef) {
-
- for (const auto& entry : type->entries) {
- if (skipSymbol(entry->symbolStatus.state)) {
- continue;
- }
-
- ResourceId id;
- if (package->id && type->id && entry->id) {
- id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
- }
-
- std::string unmangledPackage;
- std::string unmangledName = entry->name;
- if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
- // The entry name was mangled, and we successfully unmangled it.
- // Check that we want to emit this symbol.
- if (package->name != unmangledPackage) {
- // Skip the entry if it doesn't belong to the package we're writing.
- continue;
- }
- } else if (packageNameToGenerate != package->name) {
- // We are processing a mangled package name,
- // but this is a non-mangled resource.
- continue;
- }
-
- if (!isValidSymbol(unmangledName)) {
- ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
- std::stringstream err;
- err << "invalid symbol name '" << resourceName << "'";
- mError = err.str();
- return false;
- }
-
- if (type->type == ResourceType::kStyleable) {
- assert(!entry->values.empty());
-
- const Styleable* styleable = static_cast<const Styleable*>(
- entry->values.front()->value.get());
-
- // Comments are handled within this method.
- addMembersToStyleableClass(packageNameToGenerate, unmangledName, styleable,
- outTypeClassDef);
- } else {
- std::unique_ptr<ResourceMember> resourceMember =
- util::make_unique<ResourceMember>(transform(unmangledName), id);
-
- // Build the comments and annotations for this entry.
- AnnotationProcessor* processor = resourceMember->getCommentBuilder();
-
- // Add the comments from any <public> tags.
- if (entry->symbolStatus.state != SymbolState::kUndefined) {
- processor->appendComment(entry->symbolStatus.comment);
- }
-
- // Add the comments from all configurations of this entry.
- for (const auto& configValue : entry->values) {
- processor->appendComment(configValue->value->getComment());
- }
-
- // If this is an Attribute, append the format Javadoc.
- if (!entry->values.empty()) {
- if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) {
- // We list out the available values for the given attribute.
- addAttributeFormatDoc(processor, attr);
- }
- }
-
- outTypeClassDef->addMember(std::move(resourceMember));
- }
- }
- return true;
+bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
+ std::ostream* out) {
+ return Generate(package_name_to_generate, package_name_to_generate, out);
}
-bool JavaClassGenerator::generate(const StringPiece& packageNameToGenerate, std::ostream* out) {
- return generate(packageNameToGenerate, packageNameToGenerate, out);
+static void AppendJavaDocAnnotations(
+ const std::vector<std::string>& annotations,
+ AnnotationProcessor* processor) {
+ for (const std::string& annotation : annotations) {
+ std::string proper_annotation = "@";
+ proper_annotation += annotation;
+ processor->AppendComment(proper_annotation);
+ }
}
-static void appendJavaDocAnnotations(const std::vector<std::string>& annotations,
- AnnotationProcessor* processor) {
- for (const std::string& annotation : annotations) {
- std::string properAnnotation = "@";
- properAnnotation += annotation;
- processor->appendComment(properAnnotation);
- }
-}
+bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
+ const StringPiece& out_package_name,
+ std::ostream* out) {
+ ClassDefinition r_class("R", ClassQualifier::None, true);
-bool JavaClassGenerator::generate(const StringPiece& packageNameToGenerate,
- const StringPiece& outPackageName, std::ostream* out) {
+ for (const auto& package : table_->packages) {
+ for (const auto& type : package->types) {
+ if (type->type == ResourceType::kAttrPrivate) {
+ continue;
+ }
- ClassDefinition rClass("R", ClassQualifier::None, true);
+ const bool force_creation_if_empty =
+ (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
- for (const auto& package : mTable->packages) {
- for (const auto& type : package->types) {
- if (type->type == ResourceType::kAttrPrivate) {
- continue;
- }
+ std::unique_ptr<ClassDefinition> class_def =
+ util::make_unique<ClassDefinition>(ToString(type->type),
+ ClassQualifier::Static,
+ force_creation_if_empty);
- const bool forceCreationIfEmpty =
- (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
-
- std::unique_ptr<ClassDefinition> classDef = util::make_unique<ClassDefinition>(
- toString(type->type), ClassQualifier::Static, forceCreationIfEmpty);
-
- bool result = addMembersToTypeClass(packageNameToGenerate, package.get(), type.get(),
- classDef.get());
- if (!result) {
- return false;
- }
-
- if (type->type == ResourceType::kAttr) {
- // Also include private attributes in this same class.
- ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate);
- if (privType) {
- result = addMembersToTypeClass(packageNameToGenerate, package.get(), privType,
- classDef.get());
- if (!result) {
- return false;
- }
- }
- }
-
- if (type->type == ResourceType::kStyleable &&
- mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
- // When generating a public R class, we don't want Styleable to be part of the API.
- // It is only emitted for documentation purposes.
- classDef->getCommentBuilder()->appendComment("@doconly");
- }
-
- appendJavaDocAnnotations(mOptions.javadocAnnotations, classDef->getCommentBuilder());
-
- rClass.addMember(std::move(classDef));
- }
- }
-
- appendJavaDocAnnotations(mOptions.javadocAnnotations, rClass.getCommentBuilder());
-
- if (!ClassDefinition::writeJavaFile(&rClass, outPackageName, mOptions.useFinal, out)) {
+ bool result = AddMembersToTypeClass(
+ package_name_to_generate, package.get(), type.get(), class_def.get());
+ if (!result) {
return false;
- }
+ }
- out->flush();
- return true;
+ if (type->type == ResourceType::kAttr) {
+ // Also include private attributes in this same class.
+ ResourceTableType* priv_type =
+ package->FindType(ResourceType::kAttrPrivate);
+ if (priv_type) {
+ result =
+ AddMembersToTypeClass(package_name_to_generate, package.get(),
+ priv_type, class_def.get());
+ if (!result) {
+ return false;
+ }
+ }
+ }
+
+ if (type->type == ResourceType::kStyleable &&
+ options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
+ // When generating a public R class, we don't want Styleable to be part
+ // of the API.
+ // It is only emitted for documentation purposes.
+ class_def->GetCommentBuilder()->AppendComment("@doconly");
+ }
+
+ AppendJavaDocAnnotations(options_.javadoc_annotations,
+ class_def->GetCommentBuilder());
+
+ r_class.AddMember(std::move(class_def));
+ }
+ }
+
+ AppendJavaDocAnnotations(options_.javadoc_annotations,
+ r_class.GetCommentBuilder());
+
+ if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name,
+ options_.use_final, out)) {
+ return false;
+ }
+
+ out->flush();
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 901a86e..190e73b 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -17,86 +17,88 @@
#ifndef AAPT_JAVA_CLASS_GENERATOR_H
#define AAPT_JAVA_CLASS_GENERATOR_H
+#include <ostream>
+#include <string>
+
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "process/IResourceTableConsumer.h"
#include "util/StringPiece.h"
-#include <ostream>
-#include <string>
-
namespace aapt {
class AnnotationProcessor;
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 use_final = 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> javadoc_annotations;
};
/*
* 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* context_;
+ ResourceTable* table_;
+ JavaClassGeneratorOptions options_;
+ std::string error_;
};
inline const std::string& JavaClassGenerator::getError() const {
- return mError;
+ return error_;
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_JAVA_CLASS_GENERATOR_H
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index ed7c6bd..3d3d24e 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -15,154 +15,181 @@
*/
#include "java/JavaClassGenerator.h"
-#include "test/Test.h"
-#include "util/Util.h"
#include <sstream>
#include <string>
+#include "test/Test.h"
+#include "util/Util.h"
+
namespace aapt {
TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addSimple("android:id/class", ResourceId(0x01020000))
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/class", ResourceId(0x01020000))
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGenerator generator(context.get(), table.get(), {});
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
- std::stringstream out;
- EXPECT_FALSE(generator.generate("android", &out));
+ std::stringstream out;
+ EXPECT_FALSE(generator.Generate("android", &out));
}
TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addSimple("android:id/hey-man", ResourceId(0x01020000))
- .addValue("android:attr/cool.attr", ResourceId(0x01010000),
- test::AttributeBuilder(false).build())
- .addValue("android:styleable/hey.dude", ResourceId(0x01030000),
- test::StyleableBuilder()
- .addItem("android:attr/cool.attr", ResourceId(0x01010000))
- .build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/hey-man", ResourceId(0x01020000))
+ .AddValue("android:attr/cool.attr", ResourceId(0x01010000),
+ test::AttributeBuilder(false).Build())
+ .AddValue(
+ "android:styleable/hey.dude", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .AddItem("android:attr/cool.attr", ResourceId(0x01010000))
+ .Build())
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGenerator generator(context.get(), table.get(), {});
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
- std::stringstream out;
- EXPECT_TRUE(generator.generate("android", &out));
+ std::stringstream out;
+ EXPECT_TRUE(generator.Generate("android", &out));
- std::string output = out.str();
+ std::string output = out.str();
- EXPECT_NE(std::string::npos,
- output.find("public static final int hey_man=0x01020000;"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_man=0x01020000;"));
- EXPECT_NE(std::string::npos,
- output.find("public static final int[] hey_dude={"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int[] hey_dude={"));
- EXPECT_NE(std::string::npos,
- output.find("public static final int hey_dude_cool_attr=0;"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_dude_cool_attr=0;"));
}
TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addSimple("android:id/one", ResourceId(0x01020000))
- .addSimple("android:id/com.foo$two", ResourceId(0x01020001))
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/one", ResourceId(0x01020000))
+ .AddSimple("android:id/com.foo$two", ResourceId(0x01020001))
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGenerator generator(context.get(), table.get(), {});
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", "com.android.internal", &out));
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
- EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
- EXPECT_EQ(std::string::npos, output.find("two"));
- EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int one=0x01020000;"));
+ EXPECT_EQ(std::string::npos, output.find("two"));
+ EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
}
TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addSimple("android:attr/two", ResourceId(0x01010001))
- .addSimple("android:^attr-private/one", ResourceId(0x01010000))
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:attr/two", ResourceId(0x01010001))
+ .AddSimple("android:^attr-private/one", ResourceId(0x01010000))
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGenerator generator(context.get(), table.get(), {});
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final class attr"));
- EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private"));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final class attr"));
+ EXPECT_EQ(std::string::npos,
+ output.find("public static final class ^attr-private"));
}
TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
- StdErrDiagnostics diag;
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addSimple("android:id/one", ResourceId(0x01020000))
- .addSimple("android:id/two", ResourceId(0x01020001))
- .addSimple("android:id/three", ResourceId(0x01020002))
- .setSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
- .setSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
- .build();
+ StdErrDiagnostics diag;
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/one", ResourceId(0x01020000))
+ .AddSimple("android:id/two", ResourceId(0x01020001))
+ .AddSimple("android:id/three", ResourceId(0x01020002))
+ .SetSymbolState("android:id/one", ResourceId(0x01020000),
+ SymbolState::kPublic)
+ .SetSymbolState("android:id/two", ResourceId(0x01020001),
+ SymbolState::kPrivate)
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
- JavaClassGeneratorOptions options;
- options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
- {
- JavaClassGenerator generator(context.get(), table.get(), options);
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
- EXPECT_EQ(std::string::npos, output.find("two"));
- EXPECT_EQ(std::string::npos, output.find("three"));
- }
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int one=0x01020000;"));
+ EXPECT_EQ(std::string::npos, output.find("two"));
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
- options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
- {
- JavaClassGenerator generator(context.get(), table.get(), options);
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
- EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;"));
- EXPECT_EQ(std::string::npos, output.find("three"));
- }
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int one=0x01020000;"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int two=0x01020001;"));
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
- options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
- {
- JavaClassGenerator generator(context.get(), table.get(), options);
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("public static final int one=0x01020000;"));
- EXPECT_NE(std::string::npos, output.find("public static final int two=0x01020001;"));
- EXPECT_NE(std::string::npos, output.find("public static final int three=0x01020002;"));
- }
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ {
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int one=0x01020000;"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int two=0x01020001;"));
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int three=0x01020002;"));
+ }
}
/*
@@ -172,12 +199,15 @@
ResourceId{ 0x01, 0x02, 0x0000 }));
ResourceTable table;
table.setPackage(u"com.lib");
- ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
- Source{ "lib.xml", 33 }, util::make_unique<Id>()));
+ ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test"
+}, {},
+ Source{ "lib.xml", 33 },
+util::make_unique<Id>()));
ASSERT_TRUE(mTable->merge(std::move(table)));
Linker linker(mTable,
- std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()),
+ std::make_shared<MockResolver>(mTable, std::map<ResourceName,
+ResourceId>()),
{});
ASSERT_TRUE(linker.linkAndValidate());
@@ -197,126 +227,139 @@
}*/
TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .setPackageId("com.lib", 0x02)
- .addValue("android:attr/bar", ResourceId(0x01010000),
- test::AttributeBuilder(false).build())
- .addValue("com.lib:attr/bar", ResourceId(0x02010000),
- test::AttributeBuilder(false).build())
- .addValue("android:styleable/foo", ResourceId(0x01030000),
- test::StyleableBuilder()
- .addItem("android:attr/bar", ResourceId(0x01010000))
- .addItem("com.lib:attr/bar", ResourceId(0x02010000))
- .build())
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .SetPackageId("com.lib", 0x02)
+ .AddValue("android:attr/bar", ResourceId(0x01010000),
+ test::AttributeBuilder(false).Build())
+ .AddValue("com.lib:attr/bar", ResourceId(0x02010000),
+ test::AttributeBuilder(false).Build())
+ .AddValue("android:styleable/foo", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .AddItem("android:attr/bar", ResourceId(0x01010000))
+ .AddItem("com.lib:attr/bar", ResourceId(0x02010000))
+ .Build())
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGenerator generator(context.get(), table.get(), {});
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
- std::stringstream out;
- EXPECT_TRUE(generator.generate("android", &out));
+ std::stringstream out;
+ EXPECT_TRUE(generator.Generate("android", &out));
- std::string output = out.str();
- EXPECT_NE(std::string::npos, output.find("int foo_bar="));
- EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar="));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo_bar="));
+ EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar="));
}
TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addSimple("android:id/foo", ResourceId(0x01010000))
- .build();
- test::getValue<Id>(table.get(), "android:id/foo")
- ->setComment(std::string("This is a comment\n@deprecated"));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:id/foo", ResourceId(0x01010000))
+ .Build();
+ test::GetValue<Id>(table.get(), "android:id/foo")
+ ->SetComment(std::string("This is a comment\n@deprecated"));
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGenerator generator(context.get(), table.get(), {});
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
- std::string actual = out.str();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string actual = out.str();
- const char* expectedText =
-R"EOF(/**
+ const char* expectedText =
+ R"EOF(/**
* This is a comment
* @deprecated
*/
@Deprecated
public static final int foo=0x01010000;)EOF";
- EXPECT_NE(std::string::npos, actual.find(expectedText));
+ EXPECT_NE(std::string::npos, actual.find(expectedText));
}
-TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
+TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {}
-}
+TEST(JavaClassGeneratorTest,
+ CommentsForStyleablesAndNestedAttributesArePresent) {
+ Attribute attr(false);
+ attr.SetComment(StringPiece("This is an attribute"));
-TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
- Attribute attr(false);
- attr.setComment(StringPiece("This is an attribute"));
+ Styleable styleable;
+ styleable.entries.push_back(
+ Reference(test::ParseNameOrDie("android:attr/one")));
+ styleable.SetComment(StringPiece("This is a styleable"));
- Styleable styleable;
- styleable.entries.push_back(Reference(test::parseNameOrDie("android:attr/one")));
- styleable.setComment(StringPiece("This is a styleable"));
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("android:attr/one", util::make_unique<Attribute>(attr))
+ .AddValue("android:styleable/Container",
+ std::unique_ptr<Styleable>(styleable.Clone(nullptr)))
+ .Build();
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addValue("android:attr/one", util::make_unique<Attribute>(attr))
- .addValue("android:styleable/Container",
- std::unique_ptr<Styleable>(styleable.clone(nullptr)))
- .build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGeneratorOptions options;
+ options.use_final = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string actual = out.str();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGeneratorOptions options;
- options.useFinal = false;
- JavaClassGenerator generator(context.get(), table.get(), options);
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
- std::string actual = out.str();
-
- EXPECT_NE(std::string::npos, actual.find("attr name android:one"));
- EXPECT_NE(std::string::npos, actual.find("attr description"));
- EXPECT_NE(std::string::npos, actual.find(attr.getComment().data()));
- EXPECT_NE(std::string::npos, actual.find(styleable.getComment().data()));
+ EXPECT_NE(std::string::npos, actual.find("attr name android:one"));
+ EXPECT_NE(std::string::npos, actual.find("attr description"));
+ EXPECT_NE(std::string::npos, actual.find(attr.GetComment().data()));
+ EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data()));
}
TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
- Attribute attr(false);
- attr.setComment(StringPiece("removed"));
+ Attribute attr(false);
+ attr.SetComment(StringPiece("removed"));
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("android", 0x01)
- .addValue("android:attr/one", util::make_unique<Attribute>(attr))
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("android:attr/one", util::make_unique<Attribute>(attr))
+ .Build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder()
- .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
- .setNameManglerPolicy(NameManglerPolicy{ "android" })
- .build();
- JavaClassGeneratorOptions options;
- options.useFinal = false;
- JavaClassGenerator generator(context.get(), table.get(), options);
- std::stringstream out;
- ASSERT_TRUE(generator.generate("android", &out));
- std::string actual = out.str();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(
+ util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGeneratorOptions options;
+ options.use_final = false;
+ JavaClassGenerator generator(context.get(), table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.Generate("android", &out));
+ std::string actual = out.str();
- EXPECT_EQ(std::string::npos, actual.find("@attr name android:one"));
- EXPECT_EQ(std::string::npos, actual.find("@attr description"));
+ EXPECT_EQ(std::string::npos, actual.find("@attr name android:one"));
+ EXPECT_EQ(std::string::npos, actual.find("@attr description"));
- // We should find @removed only in the attribute javadoc and not anywhere else (i.e. the class
- // javadoc).
- const size_t pos = actual.find("removed");
- EXPECT_NE(std::string::npos, pos);
- EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1));
+ // We should find @removed only in the attribute javadoc and not anywhere else
+ // (i.e. the class
+ // javadoc).
+ const size_t pos = actual.find("removed");
+ EXPECT_NE(std::string::npos, pos);
+ EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index 5ff11b1..db84f29 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -14,111 +14,120 @@
* limitations under the License.
*/
-#include "Source.h"
-#include "java/AnnotationProcessor.h"
-#include "java/ClassDefinition.h"
#include "java/ManifestClassGenerator.h"
-#include "util/Maybe.h"
-#include "xml/XmlDom.h"
#include <algorithm>
+#include "Source.h"
+#include "java/AnnotationProcessor.h"
+#include "java/ClassDefinition.h"
+#include "util/Maybe.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
-static Maybe<StringPiece> extractJavaIdentifier(IDiagnostics* diag, const Source& source,
+static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag,
+ const Source& source,
const StringPiece& value) {
- const StringPiece sep = ".";
- auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+ const StringPiece sep = ".";
+ auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
- StringPiece result;
- if (iter != value.end()) {
- result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
- } else {
- result = value;
- }
+ StringPiece result;
+ if (iter != value.end()) {
+ result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
+ } else {
+ result = value;
+ }
- if (result.empty()) {
- diag->error(DiagMessage(source) << "empty symbol");
- return {};
- }
+ if (result.empty()) {
+ diag->Error(DiagMessage(source) << "empty symbol");
+ return {};
+ }
- iter = util::findNonAlphaNumericAndNotInSet(result, "_");
- if (iter != result.end()) {
- diag->error(DiagMessage(source)
- << "invalid character '" << StringPiece(iter, 1)
- << "' in '" << result << "'");
- return {};
- }
+ iter = util::FindNonAlphaNumericAndNotInSet(result, "_");
+ if (iter != result.end()) {
+ diag->Error(DiagMessage(source) << "invalid character '"
+ << StringPiece(iter, 1) << "' in '"
+ << result << "'");
+ return {};
+ }
- if (*result.begin() >= '0' && *result.begin() <= '9') {
- diag->error(DiagMessage(source) << "symbol can not start with a digit");
- return {};
- }
+ if (*result.begin() >= '0' && *result.begin() <= '9') {
+ diag->Error(DiagMessage(source) << "symbol can not start with a digit");
+ return {};
+ }
- return result;
+ return result;
}
-static bool writeSymbol(const Source& source, IDiagnostics* diag, xml::Element* el,
- ClassDefinition* classDef) {
- xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name");
- if (!attr) {
- diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
- return false;
- }
+static bool WriteSymbol(const Source& source, IDiagnostics* diag,
+ xml::Element* el, ClassDefinition* class_def) {
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ if (!attr) {
+ diag->Error(DiagMessage(source) << "<" << el->name
+ << "> must define 'android:name'");
+ return false;
+ }
- Maybe<StringPiece> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber),
- attr->value);
- if (!result) {
- return false;
- }
+ Maybe<StringPiece> result = ExtractJavaIdentifier(
+ diag, source.WithLine(el->line_number), attr->value);
+ if (!result) {
+ return false;
+ }
- std::unique_ptr<StringMember> stringMember = util::make_unique<StringMember>(
- result.value(), attr->value);
- stringMember->getCommentBuilder()->appendComment(el->comment);
+ std::unique_ptr<StringMember> string_member =
+ util::make_unique<StringMember>(result.value(), attr->value);
+ string_member->GetCommentBuilder()->AppendComment(el->comment);
- classDef->addMember(std::move(stringMember));
- return true;
+ class_def->AddMember(std::move(string_member));
+ return true;
}
-std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res) {
- xml::Element* el = xml::findRootElement(res->root.get());
- if (!el) {
- diag->error(DiagMessage(res->file.source) << "no root tag defined");
- return {};
+std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag,
+ xml::XmlResource* res) {
+ xml::Element* el = xml::FindRootElement(res->root.get());
+ if (!el) {
+ diag->Error(DiagMessage(res->file.source) << "no root tag defined");
+ return {};
+ }
+
+ if (el->name != "manifest" && !el->namespace_uri.empty()) {
+ diag->Error(DiagMessage(res->file.source)
+ << "no <manifest> root tag defined");
+ return {};
+ }
+
+ std::unique_ptr<ClassDefinition> permission_class =
+ util::make_unique<ClassDefinition>("permission", ClassQualifier::Static,
+ false);
+ std::unique_ptr<ClassDefinition> permission_group_class =
+ util::make_unique<ClassDefinition>("permission_group",
+ ClassQualifier::Static, false);
+
+ bool error = false;
+ std::vector<xml::Element*> children = el->GetChildElements();
+ for (xml::Element* child_el : children) {
+ if (child_el->namespace_uri.empty()) {
+ if (child_el->name == "permission") {
+ error |= !WriteSymbol(res->file.source, diag, child_el,
+ permission_class.get());
+ } else if (child_el->name == "permission-group") {
+ error |= !WriteSymbol(res->file.source, diag, child_el,
+ permission_group_class.get());
+ }
}
+ }
- if (el->name != "manifest" && !el->namespaceUri.empty()) {
- diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined");
- return {};
- }
+ if (error) {
+ return {};
+ }
- std::unique_ptr<ClassDefinition> permissionClass =
- util::make_unique<ClassDefinition>("permission", ClassQualifier::Static, false);
- std::unique_ptr<ClassDefinition> permissionGroupClass =
- util::make_unique<ClassDefinition>("permission_group", ClassQualifier::Static, false);
-
- bool error = false;
-
- std::vector<xml::Element*> children = el->getChildElements();
- for (xml::Element* childEl : children) {
- if (childEl->namespaceUri.empty()) {
- if (childEl->name == "permission") {
- error |= !writeSymbol(res->file.source, diag, childEl, permissionClass.get());
- } else if (childEl->name == "permission-group") {
- error |= !writeSymbol(res->file.source, diag, childEl, permissionGroupClass.get());
- }
- }
- }
-
- if (error) {
- return {};
- }
-
- std::unique_ptr<ClassDefinition> manifestClass =
- util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None, false);
- manifestClass->addMember(std::move(permissionClass));
- manifestClass->addMember(std::move(permissionGroupClass));
- return manifestClass;
+ std::unique_ptr<ClassDefinition> manifest_class =
+ util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None,
+ false);
+ manifest_class->AddMember(std::move(permission_class));
+ manifest_class->AddMember(std::move(permission_group_class));
+ return manifest_class;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index f565289..b12202a 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -19,15 +19,13 @@
#include "Diagnostics.h"
#include "java/ClassDefinition.h"
-#include "util/StringPiece.h"
#include "xml/XmlDom.h"
-#include <iostream>
-
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/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index eecb544..5ebf508 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -15,30 +15,33 @@
*/
#include "java/ManifestClassGenerator.h"
+
#include "test/Test.h"
namespace aapt {
-static ::testing::AssertionResult getManifestClassText(IAaptContext* context, xml::XmlResource* res,
- std::string* outStr) {
- std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
- context->getDiagnostics(), res);
- if (!manifestClass) {
- return ::testing::AssertionFailure() << "manifestClass == nullptr";
- }
+static ::testing::AssertionResult GetManifestClassText(IAaptContext* context,
+ xml::XmlResource* res,
+ std::string* out_str) {
+ std::unique_ptr<ClassDefinition> manifest_class =
+ GenerateManifestClass(context->GetDiagnostics(), res);
+ if (!manifest_class) {
+ return ::testing::AssertionFailure() << "manifest_class == nullptr";
+ }
- std::stringstream out;
- if (!manifestClass->writeJavaFile(manifestClass.get(), "android", true, &out)) {
- return ::testing::AssertionFailure() << "failed to write java file";
- }
+ std::stringstream out;
+ if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true,
+ &out)) {
+ return ::testing::AssertionFailure() << "failed to write java file";
+ }
- *outStr = out.str();
- return ::testing::AssertionSuccess();
+ *out_str = out.str();
+ return ::testing::AssertionSuccess();
}
TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<permission android:name="android.permission.ACCESS_INTERNET" />
<permission android:name="android.DO_DANGEROUS_THINGS" />
@@ -46,46 +49,51 @@
<permission-group android:name="foo.bar.PERMISSION" />
</manifest>)EOF");
- std::string actual;
- ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual));
+ std::string actual;
+ ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
- const size_t permissionClassPos = actual.find("public static final class permission {");
- const size_t permissionGroupClassPos =
- actual.find("public static final class permission_group {");
- ASSERT_NE(std::string::npos, permissionClassPos);
- ASSERT_NE(std::string::npos, permissionGroupClassPos);
+ const size_t permission_class_pos =
+ actual.find("public static final class permission {");
+ const size_t permission_croup_class_pos =
+ actual.find("public static final class permission_group {");
+ ASSERT_NE(std::string::npos, permission_class_pos);
+ ASSERT_NE(std::string::npos, permission_croup_class_pos);
- //
- // Make sure these permissions are in the permission class.
- //
+ //
+ // Make sure these permissions are in the permission class.
+ //
- size_t pos = actual.find("public static final String ACCESS_INTERNET="
- "\"android.permission.ACCESS_INTERNET\";");
- EXPECT_GT(pos, permissionClassPos);
- EXPECT_LT(pos, permissionGroupClassPos);
+ size_t pos = actual.find(
+ "public static final String ACCESS_INTERNET="
+ "\"android.permission.ACCESS_INTERNET\";");
+ EXPECT_GT(pos, permission_class_pos);
+ EXPECT_LT(pos, permission_croup_class_pos);
- pos = actual.find("public static final String DO_DANGEROUS_THINGS="
- "\"android.DO_DANGEROUS_THINGS\";");
- EXPECT_GT(pos, permissionClassPos);
- EXPECT_LT(pos, permissionGroupClassPos);
+ pos = actual.find(
+ "public static final String DO_DANGEROUS_THINGS="
+ "\"android.DO_DANGEROUS_THINGS\";");
+ EXPECT_GT(pos, permission_class_pos);
+ EXPECT_LT(pos, permission_croup_class_pos);
- pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";");
- EXPECT_GT(pos, permissionClassPos);
- EXPECT_LT(pos, permissionGroupClassPos);
+ pos = actual.find(
+ "public static final String HUH=\"com.test.sample.permission.HUH\";");
+ EXPECT_GT(pos, permission_class_pos);
+ EXPECT_LT(pos, permission_croup_class_pos);
- //
- // Make sure these permissions are in the permission_group class
- //
+ //
+ // Make sure these permissions are in the permission_group class
+ //
- pos = actual.find("public static final String PERMISSION="
- "\"foo.bar.PERMISSION\";");
- EXPECT_GT(pos, permissionGroupClassPos);
- EXPECT_LT(pos, std::string::npos);
+ pos = actual.find(
+ "public static final String PERMISSION="
+ "\"foo.bar.PERMISSION\";");
+ EXPECT_GT(pos, permission_croup_class_pos);
+ EXPECT_LT(pos, std::string::npos);
}
TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required to access the internet.
Added in API 1. -->
@@ -98,36 +106,36 @@
<permission android:name="android.permission.SECRET" />
</manifest>)EOF");
- std::string actual;
- ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual));
+ std::string actual;
+ ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
- const char* expectedAccessInternet =
-R"EOF( /**
+ const char* expected_access_internet =
+ R"EOF( /**
* Required to access the internet.
* Added in API 1.
*/
public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF";
- EXPECT_NE(std::string::npos, actual.find(expectedAccessInternet));
+ EXPECT_NE(std::string::npos, actual.find(expected_access_internet));
- const char* expectedPlayOutside =
-R"EOF( /**
+ const char* expected_play_outside =
+ R"EOF( /**
* @deprecated This permission is for playing outside.
*/
@Deprecated
public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF";
- EXPECT_NE(std::string::npos, actual.find(expectedPlayOutside));
+ EXPECT_NE(std::string::npos, actual.find(expected_play_outside));
- const char* expectedSecret =
-R"EOF( /**
+ const char* expected_secret =
+ R"EOF( /**
* This is a private permission for system only!
* @hide
*/
@android.annotation.SystemApi
public static final String SECRET="android.permission.SECRET";)EOF";
- EXPECT_NE(std::string::npos, actual.find(expectedSecret));
+ EXPECT_NE(std::string::npos, actual.find(expected_secret));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 902ec4c..624a559 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -15,254 +15,281 @@
*/
#include "java/ProguardRules.h"
-#include "util/Util.h"
-#include "xml/XmlDom.h"
#include <memory>
#include <string>
+#include "android-base/macros.h"
+
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
namespace proguard {
class BaseVisitor : public xml::Visitor {
-public:
- BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
+ public:
+ BaseVisitor(const Source& source, KeepSet* keep_set)
+ : source_(source), keep_set_(keep_set) {}
+
+ virtual void Visit(xml::Text*) override{};
+
+ virtual void Visit(xml::Namespace* node) override {
+ for (const auto& child : node->children) {
+ child->Accept(this);
}
+ }
- virtual void visit(xml::Text*) override {};
-
- virtual void visit(xml::Namespace* node) override {
- for (const auto& child : node->children) {
- child->accept(this);
+ virtual void Visit(xml::Element* node) override {
+ if (!node->namespace_uri.empty()) {
+ Maybe<xml::ExtractedPackage> maybe_package =
+ xml::ExtractPackageFromNamespace(node->namespace_uri);
+ if (maybe_package) {
+ // This is a custom view, let's figure out the class name from this.
+ std::string package = maybe_package.value().package + "." + node->name;
+ if (util::IsJavaClassName(package)) {
+ AddClass(node->line_number, package);
}
+ }
+ } else if (util::IsJavaClassName(node->name)) {
+ AddClass(node->line_number, node->name);
}
- virtual void visit(xml::Element* node) override {
- if (!node->namespaceUri.empty()) {
- Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
- node->namespaceUri);
- if (maybePackage) {
- // This is a custom view, let's figure out the class name from this.
- std::string package = maybePackage.value().package + "." + node->name;
- if (util::isJavaClassName(package)) {
- addClass(node->lineNumber, package);
- }
- }
- } else if (util::isJavaClassName(node->name)) {
- addClass(node->lineNumber, node->name);
- }
-
- for (const auto& child: node->children) {
- child->accept(this);
- }
+ for (const auto& child : node->children) {
+ child->Accept(this);
}
+ }
-protected:
- void addClass(size_t lineNumber, const std::string& className) {
- mKeepSet->addClass(Source(mSource.path, lineNumber), className);
- }
+ protected:
+ void AddClass(size_t line_number, const std::string& class_name) {
+ keep_set_->AddClass(Source(source_.path, line_number), class_name);
+ }
- void addMethod(size_t lineNumber, const std::string& methodName) {
- mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
- }
+ void AddMethod(size_t line_number, const std::string& method_name) {
+ keep_set_->AddMethod(Source(source_.path, line_number), method_name);
+ }
-private:
- Source mSource;
- KeepSet* mKeepSet;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
+
+ Source source_;
+ KeepSet* keep_set_;
};
-struct LayoutVisitor : public BaseVisitor {
- LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+class LayoutVisitor : public BaseVisitor {
+ public:
+ LayoutVisitor(const Source& source, KeepSet* keep_set)
+ : BaseVisitor(source, keep_set) {}
+
+ virtual void Visit(xml::Element* node) override {
+ bool check_class = false;
+ bool check_name = false;
+ if (node->namespace_uri.empty()) {
+ check_class = node->name == "view" || node->name == "fragment";
+ } else if (node->namespace_uri == xml::kSchemaAndroid) {
+ check_name = node->name == "fragment";
}
- virtual void visit(xml::Element* node) override {
- bool checkClass = false;
- bool checkName = false;
- if (node->namespaceUri.empty()) {
- checkClass = node->name == "view" || node->name == "fragment";
- } else if (node->namespaceUri == xml::kSchemaAndroid) {
- checkName = node->name == "fragment";
- }
-
- for (const auto& attr : node->attributes) {
- if (checkClass && attr.namespaceUri.empty() && attr.name == "class" &&
- util::isJavaClassName(attr.value)) {
- addClass(node->lineNumber, attr.value);
- } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid &&
- attr.name == "name" && util::isJavaClassName(attr.value)) {
- addClass(node->lineNumber, attr.value);
- } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == "onClick") {
- addMethod(node->lineNumber, attr.value);
- }
- }
-
- BaseVisitor::visit(node);
+ for (const auto& attr : node->attributes) {
+ if (check_class && attr.namespace_uri.empty() && attr.name == "class" &&
+ util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value);
+ } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid &&
+ attr.name == "name" && util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value);
+ } else if (attr.namespace_uri == xml::kSchemaAndroid &&
+ attr.name == "onClick") {
+ AddMethod(node->line_number, attr.value);
+ }
}
+
+ BaseVisitor::Visit(node);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LayoutVisitor);
};
-struct XmlResourceVisitor : public BaseVisitor {
- XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+class XmlResourceVisitor : public BaseVisitor {
+ public:
+ XmlResourceVisitor(const Source& source, KeepSet* keep_set)
+ : BaseVisitor(source, keep_set) {}
+
+ virtual void Visit(xml::Element* node) override {
+ bool check_fragment = false;
+ if (node->namespace_uri.empty()) {
+ check_fragment =
+ node->name == "PreferenceScreen" || node->name == "header";
}
- virtual void visit(xml::Element* node) override {
- bool checkFragment = false;
- if (node->namespaceUri.empty()) {
- checkFragment = node->name == "PreferenceScreen" || node->name == "header";
- }
-
- if (checkFragment) {
- xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, "fragment");
- if (attr && util::isJavaClassName(attr->value)) {
- addClass(node->lineNumber, attr->value);
- }
- }
-
- BaseVisitor::visit(node);
+ if (check_fragment) {
+ xml::Attribute* attr =
+ node->FindAttribute(xml::kSchemaAndroid, "fragment");
+ if (attr && util::IsJavaClassName(attr->value)) {
+ AddClass(node->line_number, attr->value);
+ }
}
+
+ BaseVisitor::Visit(node);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor);
};
-struct TransitionVisitor : public BaseVisitor {
- TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+class TransitionVisitor : public BaseVisitor {
+ public:
+ TransitionVisitor(const Source& source, KeepSet* keep_set)
+ : BaseVisitor(source, keep_set) {}
+
+ virtual void Visit(xml::Element* node) override {
+ bool check_class =
+ node->namespace_uri.empty() &&
+ (node->name == "transition" || node->name == "pathMotion");
+ if (check_class) {
+ xml::Attribute* attr = node->FindAttribute({}, "class");
+ if (attr && util::IsJavaClassName(attr->value)) {
+ AddClass(node->line_number, attr->value);
+ }
}
- virtual void visit(xml::Element* node) override {
- bool checkClass = node->namespaceUri.empty() &&
- (node->name == "transition" || node->name == "pathMotion");
- if (checkClass) {
- xml::Attribute* attr = node->findAttribute({}, "class");
- if (attr && util::isJavaClassName(attr->value)) {
- addClass(node->lineNumber, attr->value);
- }
- }
+ BaseVisitor::Visit(node);
+ }
- BaseVisitor::visit(node);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TransitionVisitor);
};
-struct ManifestVisitor : public BaseVisitor {
- ManifestVisitor(const Source& source, KeepSet* keepSet, bool mainDexOnly)
- : BaseVisitor(source, keepSet), mMainDexOnly(mainDexOnly) {
- }
+class ManifestVisitor : public BaseVisitor {
+ public:
+ ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)
+ : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {}
- virtual void visit(xml::Element* node) override {
- if (node->namespaceUri.empty()) {
- bool getName = false;
- if (node->name == "manifest") {
- xml::Attribute* attr = node->findAttribute({}, "package");
- if (attr) {
- mPackage = attr->value;
- }
- } else if (node->name == "application") {
- getName = true;
- xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, "backupAgent");
- if (attr) {
- Maybe<std::string> result = util::getFullyQualifiedClassName(mPackage,
- attr->value);
- if (result) {
- addClass(node->lineNumber, result.value());
- }
- }
- if (mMainDexOnly) {
- xml::Attribute* defaultProcess = node->findAttribute(xml::kSchemaAndroid,
- "process");
- if (defaultProcess) {
- mDefaultProcess = defaultProcess->value;
- }
- }
- } else if (node->name == "activity" || node->name == "service" ||
- node->name == "receiver" || node->name == "provider") {
- getName = true;
-
- if (mMainDexOnly) {
- xml::Attribute* componentProcess = node->findAttribute(xml::kSchemaAndroid,
- "process");
-
- const std::string& process = componentProcess ? componentProcess->value
- : mDefaultProcess;
- getName = !process.empty() && process[0] != ':';
- }
- } else if (node-> name == "instrumentation") {
- getName = true;
- }
-
- if (getName) {
- xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, "name");
- getName = attr != nullptr;
-
- if (getName) {
- Maybe<std::string> result = util::getFullyQualifiedClassName(mPackage,
- attr->value);
- if (result) {
- addClass(node->lineNumber, result.value());
- }
- }
- }
+ virtual void Visit(xml::Element* node) override {
+ if (node->namespace_uri.empty()) {
+ bool get_name = false;
+ if (node->name == "manifest") {
+ xml::Attribute* attr = node->FindAttribute({}, "package");
+ if (attr) {
+ package_ = attr->value;
}
- BaseVisitor::visit(node);
- }
+ } else if (node->name == "application") {
+ get_name = true;
+ xml::Attribute* attr =
+ node->FindAttribute(xml::kSchemaAndroid, "backupAgent");
+ if (attr) {
+ Maybe<std::string> result =
+ util::GetFullyQualifiedClassName(package_, attr->value);
+ if (result) {
+ AddClass(node->line_number, result.value());
+ }
+ }
+ if (main_dex_only_) {
+ xml::Attribute* default_process =
+ node->FindAttribute(xml::kSchemaAndroid, "process");
+ if (default_process) {
+ default_process_ = default_process->value;
+ }
+ }
+ } else if (node->name == "activity" || node->name == "service" ||
+ node->name == "receiver" || node->name == "provider") {
+ get_name = true;
-private:
- std::string mPackage;
- const bool mMainDexOnly;
- std::string mDefaultProcess;
+ if (main_dex_only_) {
+ xml::Attribute* component_process =
+ node->FindAttribute(xml::kSchemaAndroid, "process");
+
+ const std::string& process =
+ component_process ? component_process->value : default_process_;
+ get_name = !process.empty() && process[0] != ':';
+ }
+ } else if (node->name == "instrumentation") {
+ get_name = true;
+ }
+
+ if (get_name) {
+ xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name");
+ get_name = attr != nullptr;
+
+ if (get_name) {
+ Maybe<std::string> result =
+ util::GetFullyQualifiedClassName(package_, attr->value);
+ if (result) {
+ AddClass(node->line_number, result.value());
+ }
+ }
+ }
+ }
+ BaseVisitor::Visit(node);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
+
+ std::string package_;
+ const bool main_dex_only_;
+ std::string default_process_;
};
-bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
- KeepSet* keepSet, bool mainDexOnly) {
- ManifestVisitor visitor(source, keepSet, mainDexOnly);
- if (res->root) {
- res->root->accept(&visitor);
- return true;
- }
+bool CollectProguardRulesForManifest(const Source& source,
+ xml::XmlResource* res, KeepSet* keep_set,
+ bool main_dex_only) {
+ ManifestVisitor visitor(source, keep_set, main_dex_only);
+ if (res->root) {
+ res->root->Accept(&visitor);
+ return true;
+ }
+ return false;
+}
+
+bool CollectProguardRules(const Source& source, xml::XmlResource* res,
+ KeepSet* keep_set) {
+ if (!res->root) {
return false;
+ }
+
+ switch (res->file.name.type) {
+ case ResourceType::kLayout: {
+ LayoutVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kXml: {
+ XmlResourceVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kTransition: {
+ TransitionVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
+ }
+
+ default:
+ break;
+ }
+ return true;
}
-bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
- if (!res->root) {
- return false;
+bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
+ for (const auto& entry : keep_set.keep_set_) {
+ for (const Source& source : entry.second) {
+ *out << "# Referenced at " << source << "\n";
}
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ }
- switch (res->file.name.type) {
- case ResourceType::kLayout: {
- LayoutVisitor visitor(source, keepSet);
- res->root->accept(&visitor);
- break;
- }
-
- case ResourceType::kXml: {
- XmlResourceVisitor visitor(source, keepSet);
- res->root->accept(&visitor);
- break;
- }
-
- case ResourceType::kTransition: {
- TransitionVisitor visitor(source, keepSet);
- res->root->accept(&visitor);
- break;
- }
-
- default:
- break;
+ for (const auto& entry : keep_set.keep_method_set_) {
+ for (const Source& source : entry.second) {
+ *out << "# Referenced at " << source << "\n";
}
- return true;
+ *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n"
+ << std::endl;
+ }
+ return true;
}
-bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
- for (const auto& entry : keepSet.mKeepSet) {
- for (const Source& source : entry.second) {
- *out << "# Referenced at " << source << "\n";
- }
- *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
- }
-
- for (const auto& entry : keepSet.mKeepMethodSet) {
- for (const Source& source : entry.second) {
- *out << "# Referenced at " << source << "\n";
- }
- *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
- }
- return true;
-}
-
-} // namespace proguard
-} // namespace aapt
+} // namespace proguard
+} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index c2d2bd9..3c349ba 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -17,42 +17,44 @@
#ifndef AAPT_PROGUARD_RULES_H
#define AAPT_PROGUARD_RULES_H
-#include "Resource.h"
-#include "Source.h"
-#include "xml/XmlDom.h"
-
#include <map>
#include <ostream>
#include <set>
#include <string>
+#include "Resource.h"
+#include "Source.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
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& class_name) {
+ keep_set_[class_name].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& method_name) {
+ keep_method_set_[method_name].insert(source);
+ }
-private:
- friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+ private:
+ friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
- std::map<std::string, std::set<Source>> mKeepSet;
- std::map<std::string, std::set<Source>> mKeepMethodSet;
+ std::map<std::string, std::set<Source>> keep_set_;
+ std::map<std::string, std::set<Source>> keep_method_set_;
};
-bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet,
- bool mainDexOnly = false);
-bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool CollectProguardRulesForManifest(const Source& source,
+ xml::XmlResource* res, KeepSet* keep_set,
+ bool main_dex_only = false);
+bool CollectProguardRules(const Source& source, xml::XmlResource* res,
+ KeepSet* keep_set);
-bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
-} // 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..211ada8
--- /dev/null
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -0,0 +1,94 @@
+/*
+ * 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 "com_android_tools_aapt2_Aapt2.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+#include "util/Util.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");
+ CHECK(size_method_id != 0);
+ jint size = env->CallIntMethod(obj, size_method_id);
+ CHECK(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);
+ CHECK(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..77471ea 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -14,128 +14,142 @@
* limitations under the License.
*/
+#include "link/Linkers.h"
+
+#include <algorithm>
+
+#include "android-base/logging.h"
+
#include "ConfigDescription.h"
#include "ResourceTable.h"
#include "SdkConstants.h"
#include "ValueVisitor.h"
-#include "link/Linkers.h"
-
-#include <algorithm>
-#include <cassert>
namespace aapt {
-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);
+bool ShouldGenerateVersionedResource(const ResourceEntry* entry,
+ const ConfigDescription& config,
+ const int sdk_version_to_generate) {
+ // We assume the caller is trying to generate a version greater than the
+ // current configuration.
+ CHECK(sdk_version_to_generate > config.sdkVersion);
- const auto endIter = entry->values.end();
- auto iter = entry->values.begin();
- for (; iter != endIter; ++iter) {
- if ((*iter)->config == config) {
- break;
- }
+ const auto end_iter = entry->values.end();
+ auto iter = entry->values.begin();
+ for (; iter != end_iter; ++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.
+ CHECK(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 temp_config(config);
+ for (; iter != end_iter; ++iter) {
+ temp_config.sdkVersion = (*iter)->config.sdkVersion;
+ if (temp_config == (*iter)->config) {
+ // The two configs are the same, check the sdk version.
+ return sdk_version_to_generate < (*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;
- }
+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& 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* config_value = entry->values[i].get();
+ if (config_value->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>(config_value->value.get())) {
+ Maybe<size_t> min_sdk_stripped;
+ 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()) {
+ CHECK(bool(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 sdk_level =
+ FindAttributeSdkLevel(iter->key.id.value());
+ if (sdk_level >
+ std::max<size_t>(config_value->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 (min_sdk_stripped) {
+ min_sdk_stripped =
+ std::min(min_sdk_stripped.value(), sdk_level);
+ } else {
+ min_sdk_stripped = sdk_level;
}
+
+ // Erase this from this style.
+ iter = style->entries.erase(iter);
+ continue;
+ }
+ ++iter;
}
+
+ if (min_sdk_stripped && !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(),
+ config_value->config,
+ min_sdk_stripped.value())) {
+ // Let's create a new Style for this versioned resource.
+ ConfigDescription new_config(config_value->config);
+ new_config.sdkVersion = min_sdk_stripped.value();
+
+ std::unique_ptr<Style> new_style(
+ style->Clone(&table->string_pool));
+ new_style->SetComment(style->GetComment());
+ new_style->SetSource(style->GetSource());
+
+ // Move the previously stripped attributes into this style.
+ new_style->entries.insert(
+ new_style->entries.end(),
+ std::make_move_iterator(stripped.begin()),
+ std::make_move_iterator(stripped.end()));
+
+ // Insert the new Resource into the correct place.
+ entry->FindOrCreateValue(new_config, {})->value =
+ std::move(new_style);
+ }
+ }
+ }
}
+ }
}
- 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..755af0a 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -14,109 +14,124 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
#include "link/Linkers.h"
+
+#include "ConfigDescription.h"
#include "test/Test.h"
namespace aapt {
TEST(AutoVersionerTest, GenerateVersionedResources) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription landConfig = test::parseConfigOrDie("land");
- const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+ const ConfigDescription sw600dp_land_config =
+ 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>(
+ ConfigDescription::DefaultConfig(), ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(land_config, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
- EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
- EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
+ EXPECT_TRUE(ShouldGenerateVersionedResource(
+ &entry, ConfigDescription::DefaultConfig(), 17));
+ EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, land_config, 17));
}
TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
- const ConfigDescription defaultConfig = {};
- const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
- const ConfigDescription v21Config = test::parseConfigOrDie("v21");
+ const ConfigDescription sw600dp_v13_config =
+ test::ParseConfigOrDie("sw600dp-v13");
+ const ConfigDescription v21_config = 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>(
+ ConfigDescription::DefaultConfig(), ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(sw600dp_v13_config, ""));
+ entry.values.push_back(
+ util::make_unique<ResourceConfigValue>(v21_config, ""));
- EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
- EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
+ EXPECT_TRUE(ShouldGenerateVersionedResource(
+ &entry, ConfigDescription::DefaultConfig(), 17));
+ EXPECT_FALSE(ShouldGenerateVersionedResource(
+ &entry, ConfigDescription::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 c236394..717978ea 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -14,6 +14,17 @@
* limitations under the License.
*/
+#include <sys/stat.h>
+
+#include <fstream>
+#include <queue>
+#include <unordered_map>
+#include <vector>
+
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "google/protobuf/io/coded_stream.h"
+
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
@@ -31,9 +42,8 @@
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/Linkers.h"
-#include "link/ProductFilter.h"
-#include "link/ReferenceLinker.h"
#include "link/ManifestFixer.h"
+#include "link/ReferenceLinker.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -44,1910 +54,2059 @@
#include "util/StringPiece.h"
#include "xml/XmlDom.h"
-#include <android-base/file.h>
-#include <google/protobuf/io/coded_stream.h>
-
-#include <fstream>
-#include <queue>
-#include <sys/stat.h>
-#include <unordered_map>
-#include <vector>
-
-using google::protobuf::io::CopyingOutputStreamAdaptor;
+using ::google::protobuf::io::CopyingOutputStreamAdaptor;
namespace aapt {
struct LinkOptions {
- std::string outputPath;
- std::string manifestPath;
- std::vector<std::string> includePaths;
- std::vector<std::string> overlayFiles;
+ std::string output_path;
+ std::string manifest_path;
+ std::vector<std::string> include_paths;
+ std::vector<std::string> overlay_files;
+ bool output_to_directory = false;
+ bool auto_add_overlay = false;
- // 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> generate_java_class_path;
+ Maybe<std::string> custom_java_package;
+ std::set<std::string> extra_java_packages;
+ Maybe<std::string> generate_proguard_rules_path;
+ Maybe<std::string> generate_main_dex_proguard_rules_path;
+ bool generate_non_final_ids = false;
+ std::vector<std::string> javadoc_annotations;
+ Maybe<std::string> private_symbols;
- bool noAutoVersion = false;
- bool noVersionVectors = 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;
+ // Optimizations/features.
+ bool no_auto_version = false;
+ bool no_version_vectors = false;
+ bool no_resource_deduping = false;
+ bool no_xml_namespaces = false;
+ bool do_not_compress_anything = false;
+ std::unordered_set<std::string> extensions_to_not_compress;
- // Split APK options.
- TableSplitterOptions tableSplitterOptions;
- std::vector<SplitConstraints> splitConstraints;
- std::vector<std::string> splitPaths;
+ // Static lib options.
+ bool static_lib = false;
+ bool no_static_lib_packages = false;
- // Stable ID options.
- std::unordered_map<ResourceName, ResourceId> stableIdMap;
- Maybe<std::string> resourceIdMapPath;
+ // AndroidManifest.xml massaging options.
+ ManifestFixerOptions manifest_fixer_options;
+
+ // Products to use/filter on.
+ std::unordered_set<std::string> products;
+
+ // Split APK options.
+ TableSplitterOptions table_splitter_options;
+ std::vector<SplitConstraints> split_constraints;
+ std::vector<std::string> split_paths;
+
+ // Stable ID options.
+ std::unordered_map<ResourceName, ResourceId> stable_id_map;
+ Maybe<std::string> resource_id_map_path;
};
class LinkContext : public IAaptContext {
-public:
- LinkContext() : mNameMangler({}) {
- }
+ public:
+ LinkContext() : name_mangler_({}) {}
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ NameMangler* GetNameMangler() override { return &name_mangler_; }
- void setNameManglerPolicy(const NameManglerPolicy& policy) {
- mNameMangler = NameMangler(policy);
- }
+ void SetNameManglerPolicy(const NameManglerPolicy& policy) {
+ name_mangler_ = NameMangler(policy);
+ }
- const std::string& getCompilationPackage() override {
- return mCompilationPackage;
- }
+ const std::string& GetCompilationPackage() override {
+ return compilation_package_;
+ }
- void setCompilationPackage(const StringPiece& packageName) {
- mCompilationPackage = packageName.toString();
- }
+ void SetCompilationPackage(const StringPiece& package_name) {
+ compilation_package_ = package_name.ToString();
+ }
- uint8_t getPackageId() override {
- return mPackageId;
- }
+ uint8_t GetPackageId() override { return package_id_; }
- void setPackageId(uint8_t id) {
- mPackageId = id;
- }
+ void SetPackageId(uint8_t id) { package_id_ = id; }
- SymbolTable* getExternalSymbols() override {
- return &mSymbols;
- }
+ SymbolTable* GetExternalSymbols() override { return &symbols_; }
- bool verbose() override {
- return mVerbose;
- }
+ bool IsVerbose() override { return verbose_; }
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ void SetVerbose(bool val) { verbose_ = val; }
- int getMinSdkVersion() override {
- return mMinSdkVersion;
- }
+ int GetMinSdkVersion() override { return min_sdk_version_; }
- void setMinSdkVersion(int minSdk) {
- mMinSdkVersion = minSdk;
- }
+ void SetMinSdkVersion(int minSdk) { min_sdk_version_ = minSdk; }
-private:
- StdErrDiagnostics mDiagnostics;
- NameMangler mNameMangler;
- std::string mCompilationPackage;
- uint8_t mPackageId = 0x0;
- SymbolTable mSymbols;
- bool mVerbose = false;
- int mMinSdkVersion = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LinkContext);
+
+ StdErrDiagnostics diagnostics_;
+ NameMangler name_mangler_;
+ std::string compilation_package_;
+ uint8_t package_id_ = 0x0;
+ SymbolTable symbols_;
+ bool verbose_ = false;
+ int min_sdk_version_ = 0;
};
-static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
- uint32_t compressionFlags,
+static bool CopyFileToArchive(io::IFile* file, const std::string& out_path,
+ uint32_t compression_flags,
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);
+ 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 buffer_size = data->size();
+
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path
+ << " to archive");
+ }
+
+ if (writer->StartEntry(out_path, compression_flags)) {
+ if (writer->WriteEntry(buffer, buffer_size)) {
+ if (writer->FinishEntry()) {
+ return true;
+ }
+ }
+ }
+
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to write file "
+ << out_path);
+ 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* xml_res, const StringPiece& path,
+ Maybe<size_t> max_sdk_level, bool keep_raw_values,
+ IArchiveWriter* writer, IAaptContext* context) {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions options = {};
+ options.keep_raw_values = keep_raw_values;
+ options.max_sdk_level = max_sdk_level;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(context, xml_res)) {
return false;
+ }
+
+ if (context->IsVerbose()) {
+ DiagMessage msg;
+ msg << "writing " << path << " to archive";
+ if (max_sdk_level) {
+ msg << " maxSdkLevel=" << max_sdk_level.value()
+ << " keepRawValues=" << keep_raw_values;
+ }
+ 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,
+static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source,
+ 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 pb_table;
+ if (!pb_table.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(pb_table, 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 no_auto_version = false;
+ bool no_version_vectors = false;
+ bool no_xml_namespaces = false;
+ bool keep_raw_values = false;
+ bool do_not_compress_anything = false;
+ bool update_proguard_spec = false;
+ std::unordered_set<std::string> extensions_to_not_compress;
};
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* keep_set)
+ : options_(options), context_(context), keep_set_(keep_set) {}
- bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+ bool Flatten(ResourceTable* table, IArchiveWriter* archive_writer);
-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* file_to_copy;
- // The XML to process and flatten.
- std::unique_ptr<xml::XmlResource> xmlToFlatten;
+ // The XML to process and flatten.
+ std::unique_ptr<xml::XmlResource> xml_to_flatten;
- // The destination to write this file to.
- std::string dstPath;
- bool skipVersion = false;
- };
+ // The destination to write this file to.
+ std::string dst_path;
+ bool skip_version = 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* file_op,
+ std::queue<FileOperation>* out_file_op_queue);
- ResourceFileFlattenerOptions mOptions;
- IAaptContext* mContext;
- proguard::KeepSet* mKeepSet;
+ ResourceFileFlattenerOptions options_;
+ IAaptContext* context_;
+ proguard::KeepSet* keep_set_;
};
-uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
- if (mOptions.doNotCompressAnything) {
- return 0;
- }
+uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) {
+ if (options_.do_not_compress_anything) {
+ return 0;
+ }
- for (const std::string& extension : mOptions.extensionsToNotCompress) {
- if (util::stringEndsWith(str, extension)) {
- return 0;
- }
+ for (const std::string& extension : options_.extensions_to_not_compress) {
+ if (util::EndsWith(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* file_op,
+ std::queue<FileOperation>* out_file_op_queue) {
+ xml::XmlResource* doc = file_op->xml_to_flatten.get();
+ const Source& src = doc->file.source;
- if (mContext->verbose()) {
- mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage() << "linking " << src.path);
+ }
+
+ XmlReferenceLinker xml_linker;
+ if (!xml_linker.Consume(context_, doc)) {
+ return false;
+ }
+
+ if (options_.update_proguard_spec &&
+ !proguard::CollectProguardRules(src, doc, keep_set_)) {
+ return false;
+ }
+
+ if (options_.no_xml_namespaces) {
+ XmlNamespaceRemover namespace_remover;
+ if (!namespace_remover.Consume(context_, 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 (!options_.no_auto_version) {
+ if (options_.no_version_vectors) {
+ // Skip this if it is a vector or animated-vector.
+ xml::Element* el = xml::FindRootElement(doc);
+ if (el && el->namespace_uri.empty()) {
+ if (el->name == "vector" || el->name == "animated-vector") {
+ // We are NOT going to version this file.
+ file_op->skip_version = 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 = file_op->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 min_sdk_version = context_->GetMinSdkVersion();
+ for (int sdk_level : xml_linker.sdk_levels()) {
+ if (sdk_level > min_sdk_version && sdk_level > config.sdkVersion) {
+ if (!ShouldGenerateVersionedResource(file_op->entry, config,
+ sdk_level)) {
+ // If we shouldn't generate a versioned resource, stop checking.
+ break;
}
- const ConfigDescription& config = fileOp->config;
+ ResourceFile versioned_file_desc = doc->file;
+ versioned_file_desc.config.sdkVersion = (uint16_t)sdk_level;
- // 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 new_file_op;
+ new_file_op.xml_to_flatten = util::make_unique<xml::XmlResource>(
+ versioned_file_desc, doc->root->Clone());
+ new_file_op.config = versioned_file_desc.config;
+ new_file_op.entry = file_op->entry;
+ new_file_op.dst_path = ResourceUtils::BuildResourceFileName(
+ versioned_file_desc, context_->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 (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage(versioned_file_desc.source)
+ << "auto-versioning resource from config '" << config << "' -> '"
+ << versioned_file_desc.config << "'");
}
+
+ bool added = table->AddFileReferenceAllowMangled(
+ versioned_file_desc.name, versioned_file_desc.config,
+ versioned_file_desc.source, new_file_op.dst_path, nullptr,
+ context_->GetDiagnostics());
+ if (!added) {
+ return false;
+ }
+
+ out_file_op_queue->push(std::move(new_file_op));
+ 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* archive_writer) {
+ bool error = false;
+ std::map<std::pair<ConfigDescription, StringPiece>, FileOperation>
+ config_sorted_files;
- 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.
+ config_sorted_files.clear();
+ std::queue<FileOperation> file_operations;
- // 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& config_value : entry->values) {
+ FileReference* file_ref =
+ ValueCast<FileReference>(config_value->value.get());
+ if (!file_ref) {
continue;
- }
+ }
- auto iter = std::find(line.begin(), line.end(), '=');
- if (iter == line.end()) {
- diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
+ io::IFile* file = file_ref->file;
+ if (!file) {
+ context_->GetDiagnostics()->Error(DiagMessage(file_ref->GetSource())
+ << "file not found");
return false;
+ }
+
+ FileOperation file_op;
+ file_op.entry = entry.get();
+ file_op.dst_path = *file_ref->path;
+ file_op.config = config_value->config;
+
+ const StringPiece src_path = file->GetSource().path;
+ if (type->type != ResourceType::kRaw &&
+ (util::EndsWith(src_path, ".xml.flat") ||
+ util::EndsWith(src_path, ".xml"))) {
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to open file");
+ return false;
+ }
+
+ file_op.xml_to_flatten =
+ xml::Inflate(data->data(), data->size(),
+ context_->GetDiagnostics(), file->GetSource());
+
+ if (!file_op.xml_to_flatten) {
+ return false;
+ }
+
+ file_op.xml_to_flatten->file.config = config_value->config;
+ file_op.xml_to_flatten->file.source = file_ref->GetSource();
+ file_op.xml_to_flatten->file.name =
+ ResourceName(pkg->name, type->type, entry->name);
+
+ // Enqueue the XML files to be processed.
+ file_operations.push(std::move(file_op));
+ } else {
+ file_op.file_to_copy = 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 entry_name(entry->name);
+ config_sorted_files[std::make_pair(
+ config_value->config, entry_name)] = std::move(file_op);
+ }
+ }
+ }
+
+ // Now process the XML queue
+ for (; !file_operations.empty(); file_operations.pop()) {
+ FileOperation& file_op = file_operations.front();
+
+ if (!LinkAndVersionXmlFile(table, &file_op, &file_operations)) {
+ 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 entry_name(file_op.entry->name);
+ config_sorted_files[std::make_pair(file_op.config, entry_name)] =
+ std::move(file_op);
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Now flatten the sorted values.
+ for (auto& map_entry : config_sorted_files) {
+ const ConfigDescription& config = map_entry.first.first;
+ const FileOperation& file_op = map_entry.second;
+
+ if (file_op.xml_to_flatten) {
+ Maybe<size_t> max_sdk_level;
+ if (!options_.no_auto_version && !file_op.skip_version) {
+ max_sdk_level =
+ std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
+ context_->GetMinSdkVersion());
+ }
+
+ bool result = FlattenXml(
+ file_op.xml_to_flatten.get(), file_op.dst_path, max_sdk_level,
+ options_.keep_raw_values, archive_writer, context_);
+ if (!result) {
+ error = true;
+ }
+ } else {
+ bool result = CopyFileToArchive(
+ file_op.file_to_copy, file_op.dst_path,
+ GetCompressionFlags(file_op.dst_path), archive_writer, context_);
+ 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 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;
+static bool WriteStableIdMapToPath(
+ IDiagnostics* diag,
+ const std::unordered_map<ResourceName, ResourceId>& id_map,
+ const std::string& id_map_path) {
+ std::ofstream fout(id_map_path, std::ofstream::binary);
+ if (!fout) {
+ diag->Error(DiagMessage(id_map_path) << strerror(errno));
+ return false;
+ }
+
+ for (const auto& entry : id_map) {
+ const ResourceName& name = entry.first;
+ const ResourceId& id = entry.second;
+ fout << name << " = " << id << "\n";
+ }
+
+ if (!fout) {
+ diag->Error(DiagMessage(id_map_path)
+ << "failed writing to file: "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static bool LoadStableIdMap(
+ IDiagnostics* diag, const std::string& path,
+ std::unordered_map<ResourceName, ResourceId>* out_id_map) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content)) {
+ diag->Error(DiagMessage(path) << "failed reading stable ID file");
+ return false;
+ }
+
+ out_id_map->clear();
+ size_t line_no = 0;
+ for (StringPiece line : util::Tokenize(content, '\n')) {
+ line_no++;
+ line = util::TrimWhitespace(line);
+ if (line.empty()) {
+ continue;
}
- *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;
- }
+
+ auto iter = std::find(line.begin(), line.end(), '=');
+ if (iter == line.end()) {
+ diag->Error(DiagMessage(Source(path, line_no)) << "missing '='");
+ return false;
}
- outSplit->configs.insert(configs.begin(), configs.end());
- return true;
+
+ ResourceNameRef name;
+ StringPiece res_name_str =
+ util::TrimWhitespace(line.substr(0, std::distance(line.begin(), iter)));
+ if (!ResourceUtils::ParseResourceName(res_name_str, &name)) {
+ diag->Error(DiagMessage(Source(path, line_no))
+ << "invalid resource name '" << res_name_str << "'");
+ return false;
+ }
+
+ const size_t res_id_start_idx = std::distance(line.begin(), iter) + 1;
+ const size_t res_id_str_len = line.size() - res_id_start_idx;
+ StringPiece res_id_str =
+ util::TrimWhitespace(line.substr(res_id_start_idx, res_id_str_len));
+
+ Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(res_id_str);
+ if (!maybe_id) {
+ diag->Error(DiagMessage(Source(path, line_no)) << "invalid resource ID '"
+ << res_id_str << "'");
+ return false;
+ }
+
+ (*out_id_map)[name.ToResourceName()] = maybe_id.value();
+ }
+ return true;
+}
+
+static bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
+ std::string* out_path,
+ SplitConstraints* out_split) {
+ 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;
+ }
+ *out_path = parts[0];
+ std::vector<ConfigDescription> configs;
+ for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
+ configs.push_back({});
+ if (!ConfigDescription::Parse(config_str, &configs.back())) {
+ diag->Error(DiagMessage() << "invalid config '" << config_str
+ << "' in split parameter '" << arg << "'");
+ return false;
+ }
+ }
+ out_split->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)
+ : options_(options),
+ context_(context),
+ final_table_(),
+ file_collection_(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> asset_source =
+ util::make_unique<AssetManagerSymbolSource>();
+ for (const std::string& path : options_.include_paths) {
+ if (context_->IsVerbose()) {
+ context_->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;
- }
+ // First try to load the file as a static lib.
+ std::string error_str;
+ std::unique_ptr<ResourceTable> static_include =
+ LoadStaticLibrary(path, &error_str);
+ if (static_include) {
+ if (!options_.static_lib) {
+ // Can't include static libraries when not building a static library.
+ context_->GetDiagnostics()->Error(
+ DiagMessage(path)
+ << "can't include static library when building app");
+ 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;
- }
- }
- }
- }
- }
+ // If we are using --no-static-lib-packages, we need to rename the
+ // package of this
+ // table to our compilation package.
+ if (options_.no_static_lib_packages) {
+ if (ResourceTablePackage* pkg =
+ static_include->FindPackageById(0x7f)) {
+ pkg->name = context_->GetCompilationPackage();
+ }
}
- auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
- isExtPackageFunc);
- mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
- return !error;
- }
+ context_->GetExternalSymbols()->AppendSource(
+ util::make_unique<ResourceTableSymbolSource>(static_include.get()));
- /**
- * 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;
- }
+ static_table_includes_.push_back(std::move(static_include));
- 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");
+ } else if (!error_str.empty()) {
+ // We had an error with reading, so fail.
+ context_->GetDiagnostics()->Error(DiagMessage(path) << error_str);
return false;
+ }
+
+ if (!asset_source->AddAssetPath(path)) {
+ context_->GetDiagnostics()->Error(DiagMessage(path)
+ << "failed to load include path");
+ 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;
- }
+ context_->GetExternalSymbols()->AppendSource(std::move(asset_source));
+ return true;
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor adaptor(writer);
+ Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res,
+ IDiagnostics* diag) {
+ // Make sure the first element is <manifest> with package attribute.
+ if (xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get())) {
+ AppInfo app_info;
- std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
- if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
- return false;
- }
- }
+ if (!manifest_el->namespace_uri.empty() ||
+ manifest_el->name != "manifest") {
+ diag->Error(DiagMessage(xml_res->file.source)
+ << "root tag must be <manifest>");
+ return {};
+ }
- if (!writer->finishEntry()) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
- return false;
+ xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+ if (!package_attr) {
+ diag->Error(DiagMessage(xml_res->file.source)
+ << "<manifest> must have a 'package' attribute");
+ return {};
+ }
+
+ app_info.package = package_attr->value;
+
+ if (xml::Attribute* version_code_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
+ Maybe<uint32_t> maybe_code =
+ ResourceUtils::ParseInt(version_code_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(
+ manifest_el->line_number))
+ << "invalid android:versionCode '"
+ << version_code_attr->value << "'");
+ return {};
}
- return true;
+ app_info.version_code = maybe_code.value();
+ }
+
+ if (xml::Attribute* revision_code_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
+ Maybe<uint32_t> maybe_code =
+ ResourceUtils::ParseInt(revision_code_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(
+ manifest_el->line_number))
+ << "invalid android:revisionCode '"
+ << revision_code_attr->value << "'");
+ return {};
+ }
+ app_info.revision_code = maybe_code.value();
+ }
+
+ if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(
+ xml::kSchemaAndroid, "minSdkVersion")) {
+ app_info.min_sdk_version = min_sdk->value;
+ }
+ }
+ return app_info;
}
+ return {};
+ }
- bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
- const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) {
- if (!mOptions.generateJavaClassPath) {
- return true;
- }
+ /**
+ * 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 is_ext_package_func =
+ [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
+ return context_->GetCompilationPackage() != pkg->name || !pkg->id ||
+ pkg->id.value() != context_->GetPackageId();
+ };
- 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;
- }
+ bool error = false;
+ for (const auto& package : final_table_.packages) {
+ if (is_ext_package_func(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 res_name(package->name, type->type, entry->name);
- 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)) {
+ for (const auto& config_value : 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>(config_value->value.get()) &&
+ package->name == "android") {
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(config_value->value->GetSource())
+ << "generated id '" << res_name
+ << "' for external package '" << package->name << "'");
+ } else {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(config_value->value->GetSource())
+ << "defined resource '" << res_name
+ << "' for external package '" << package->name << "'");
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);
+ auto new_end_iter =
+ std::remove_if(final_table_.packages.begin(),
+ final_table_.packages.end(), is_ext_package_func);
+ final_table_.packages.erase(new_end_iter, final_table_.packages.end());
+ return !error;
+ }
+
+ /**
+ * Returns true if no IDs have been set, false otherwise.
+ */
+ bool VerifyNoIdsSet() {
+ for (const auto& package : final_table_.packages) {
+ for (const auto& type : package->types) {
+ if (type->id) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "type " << type->type << " has ID " << std::hex
+ << (int)type->id.value() << std::dec
+ << " assigned");
+ return false;
}
- 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) {
+ for (const auto& entry : type->entries) {
+ if (entry->id) {
+ ResourceNameRef res_name(package->name, type->type, entry->name);
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "entry " << res_name << " has ID " << std::hex
+ << (int)entry->id.value() << std::dec
+ << " assigned");
return false;
+ }
}
+ }
+ }
+ return true;
+ }
- 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);
+ std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
+ if (options_.output_to_directory) {
+ return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
+ } else {
+ return CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
+ }
+ }
- 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;
+ bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(&buffer);
+ if (!flattener.Consume(context_, table)) {
+ return false;
}
- 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;
+ if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->WriteEntry(buffer)) {
+ if (writer->FinishEntry()) {
+ return true;
}
-
- // 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;
- }
- }
-
- 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 {
- error = true;
- }
- }
-
- if (error) {
- mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
- return 1;
- }
-
- if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) {
- return 1;
- }
-
- 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 (!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;
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
+ }
- std::unique_ptr<TableMerger> mTableMerger;
+ bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
+ // Create the file/zip entry.
+ if (!writer->StartEntry("resources.arsc.flat", 0)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to open");
+ return false;
+ }
- // A pointer to the FileCollection representing the filesystem (not archives).
- std::unique_ptr<io::FileCollection> mFileCollection;
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream interface.
+ CopyingOutputStreamAdaptor adaptor(writer);
- // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
- std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
+ if (!pb_table->SerializeToZeroCopyStream(&adaptor)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to write");
+ return 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;
+ if (!writer->FinishEntry()) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to finish entry");
+ return false;
+ }
+ return true;
+ }
+
+ bool WriteJavaFile(ResourceTable* table,
+ const StringPiece& package_name_to_generate,
+ const StringPiece& out_package,
+ const JavaClassGeneratorOptions& java_options) {
+ if (!options_.generate_java_class_path) {
+ return true;
+ }
+
+ std::string out_path = options_.generate_java_class_path.value();
+ file::AppendPath(&out_path, file::PackageToPath(out_package));
+ if (!file::mkdirs(out_path)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create directory '" << out_path << "'");
+ return false;
+ }
+
+ file::AppendPath(&out_path, "R.java");
+
+ std::ofstream fout(out_path, std::ofstream::binary);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+
+ JavaClassGenerator generator(context_, table, java_options);
+ if (!generator.Generate(package_name_to_generate, out_package, &fout)) {
+ context_->GetDiagnostics()->Error(DiagMessage(out_path)
+ << generator.getError());
+ return false;
+ }
+
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ }
+ return true;
+ }
+
+ bool WriteManifestJavaFile(xml::XmlResource* manifest_xml) {
+ if (!options_.generate_java_class_path) {
+ return true;
+ }
+
+ std::unique_ptr<ClassDefinition> manifest_class =
+ GenerateManifestClass(context_->GetDiagnostics(), manifest_xml);
+
+ if (!manifest_class) {
+ // Something bad happened, but we already logged it, so exit.
+ return false;
+ }
+
+ if (manifest_class->empty()) {
+ // Empty Manifest class, no need to generate it.
+ return true;
+ }
+
+ // Add any JavaDoc annotations to the generated class.
+ for (const std::string& annotation : options_.javadoc_annotations) {
+ std::string proper_annotation = "@";
+ proper_annotation += annotation;
+ manifest_class->GetCommentBuilder()->AppendComment(proper_annotation);
+ }
+
+ const std::string& package_utf8 = context_->GetCompilationPackage();
+
+ std::string out_path = options_.generate_java_class_path.value();
+ file::AppendPath(&out_path, file::PackageToPath(package_utf8));
+
+ if (!file::mkdirs(out_path)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create directory '" << out_path << "'");
+ return false;
+ }
+
+ file::AppendPath(&out_path, "Manifest.java");
+
+ std::ofstream fout(out_path, std::ofstream::binary);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+
+ if (!ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8,
+ true, &fout)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+ return true;
+ }
+
+ bool WriteProguardFile(const Maybe<std::string>& out,
+ const proguard::KeepSet& keep_set) {
+ if (!out) {
+ return true;
+ }
+
+ const std::string& out_path = out.value();
+ std::ofstream fout(out_path, std::ofstream::binary);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to open '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+
+ proguard::WriteKeepSet(&fout, keep_set);
+ if (!fout) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed writing to '" << out_path << "': "
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
+ }
+ return true;
+ }
+
+ std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input,
+ std::string* out_error) {
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::Create(input, out_error);
+ 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(),
+ context_->GetDiagnostics());
+ }
+
+ bool MergeStaticLibrary(const std::string& input, bool override) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage()
+ << "merging static library " << input);
+ }
+
+ std::string error_str;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::Create(input, &error_str);
+ if (!collection) {
+ context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table =
+ LoadTablePbFromCollection(collection.get());
+ if (!table) {
+ context_->GetDiagnostics()->Error(DiagMessage(input)
+ << "invalid static library");
+ return false;
+ }
+
+ ResourceTablePackage* pkg = table->FindPackageById(0x7f);
+ if (!pkg) {
+ context_->GetDiagnostics()->Error(DiagMessage(input)
+ << "static library has no package");
+ return false;
+ }
+
+ bool result;
+ if (options_.no_static_lib_packages) {
+ // Merge all resources as if they were in the compilation package. This is
+ // the old behavior 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()) {
+ options_.extra_java_packages.insert(pkg->name);
+ }
+
+ pkg->name = "";
+ if (override) {
+ result = table_merger_->MergeOverlay(Source(input), table.get(),
+ collection.get());
+ } else {
+ result =
+ table_merger_->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 = table_merger_->MergeAndMangle(Source(input), pkg->name,
+ table.get(), collection.get());
+ }
+
+ if (!result) {
+ return false;
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ collections_.push_back(std::move(collection));
+ return true;
+ }
+
+ bool MergeResourceTable(io::IFile* file, bool override) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "merging resource table " << file->GetSource());
+ }
+
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to open file");
+ return false;
+ }
+
+ std::unique_ptr<ResourceTable> table =
+ LoadTableFromPb(file->GetSource(), data->data(), data->size(),
+ context_->GetDiagnostics());
+ if (!table) {
+ return false;
+ }
+
+ bool result = false;
+ if (override) {
+ result = table_merger_->MergeOverlay(file->GetSource(), table.get());
+ } else {
+ result = table_merger_->Merge(file->GetSource(), table.get());
+ }
+ return result;
+ }
+
+ bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc,
+ bool override) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "merging '" << file_desc->name
+ << "' from compiled file " << file->GetSource());
+ }
+
+ bool result = false;
+ if (override) {
+ result = table_merger_->MergeFileOverlay(*file_desc, file);
+ } else {
+ result = table_merger_->MergeFile(*file_desc, file);
+ }
+
+ if (!result) {
+ return false;
+ }
+
+ // Add the exports of this file to the table.
+ for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) {
+ if (exported_symbol.name.package.empty()) {
+ exported_symbol.name.package = context_->GetCompilationPackage();
+ }
+
+ ResourceNameRef res_name = exported_symbol.name;
+
+ Maybe<ResourceName> mangled_name =
+ context_->GetNameMangler()->MangleName(exported_symbol.name);
+ if (mangled_name) {
+ res_name = mangled_name.value();
+ }
+
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->SetSource(file_desc->source.WithLine(exported_symbol.line));
+ bool result = final_table_.AddResourceAllowMangled(
+ res_name, ConfigDescription::DefaultConfig(), std::string(),
+ std::move(id), context_->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 (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage() << "merging archive "
+ << input);
+ }
+
+ std::string error_str;
+ std::unique_ptr<io::ZipFileCollection> collection =
+ io::ZipFileCollection::Create(input, &error_str);
+ if (!collection) {
+ context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
+ 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.
+ collections_.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::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") ||
+ util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) {
+ return MergeArchive(path, override);
+ } else if (util::EndsWith(path, ".apk")) {
+ return MergeStaticLibrary(path, override);
+ }
+
+ io::IFile* file = file_collection_->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::EndsWith(src.path, ".arsc.flat")) {
+ return MergeResourceTable(file, override);
+
+ } else if (util::EndsWith(src.path, ".flat")) {
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open");
+ return false;
+ }
+
+ CompiledFileInputStream input_stream(data->data(), data->size());
+ uint32_t num_files = 0;
+ if (!input_stream.ReadLittleEndian32(&num_files)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed read num files");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < num_files; i++) {
+ pb::CompiledFile compiled_file;
+ if (!input_stream.ReadCompiledFile(&compiled_file)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "failed to read compiled file header");
+ return false;
+ }
+
+ uint64_t offset, len;
+ if (!input_stream.ReadDataMetaData(&offset, &len)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed to read data meta data");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resource_file =
+ DeserializeCompiledFileFromPb(compiled_file, file->GetSource(),
+ context_->GetDiagnostics());
+ if (!resource_file) {
+ return false;
+ }
+
+ if (!MergeCompiledFile(file->CreateFileSegment(offset, len),
+ resource_file.get(), override)) {
+ return false;
+ }
+ }
+ return true;
+ } else if (util::EndsWith(src.path, ".xml") ||
+ util::EndsWith(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 file_type =
+ util::EndsWith(src.path, ".xml") ? "XML" : "PNG";
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "uncompiled " << file_type
+ << " 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& app_info, const SplitConstraints& constraints) {
+ std::unique_ptr<xml::XmlResource> doc =
+ util::make_unique<xml::XmlResource>();
+
+ std::unique_ptr<xml::Namespace> namespace_android =
+ util::make_unique<xml::Namespace>();
+ namespace_android->namespace_uri = xml::kSchemaAndroid;
+ namespace_android->namespace_prefix = "android";
+
+ std::unique_ptr<xml::Element> manifest_el =
+ util::make_unique<xml::Element>();
+ manifest_el->name = "manifest";
+ manifest_el->attributes.push_back(
+ xml::Attribute{"", "package", app_info.package});
+
+ if (app_info.version_code) {
+ manifest_el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCode",
+ std::to_string(app_info.version_code.value())});
+ }
+
+ if (app_info.revision_code) {
+ manifest_el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "revisionCode",
+ std::to_string(app_info.revision_code.value())});
+ }
+
+ std::stringstream split_name;
+ split_name << "config." << util::Joiner(constraints.configs, "_");
+
+ manifest_el->attributes.push_back(
+ xml::Attribute{"", "split", split_name.str()});
+
+ std::unique_ptr<xml::Element> application_el =
+ util::make_unique<xml::Element>();
+ application_el->name = "application";
+ application_el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
+
+ manifest_el->AddChild(std::move(application_el));
+ namespace_android->AddChild(std::move(manifest_el));
+ doc->root = std::move(namespace_android);
+ return doc;
+ }
+
+ /**
+ * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
+ * the ResourceTable to the IArchiveWriter.
+ */
+ bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set,
+ xml::XmlResource* manifest, ResourceTable* table) {
+ const bool keep_raw_values = options_.static_lib;
+ bool result = FlattenXml(manifest, "AndroidManifest.xml", {},
+ keep_raw_values, writer, context_);
+ if (!result) {
+ return false;
+ }
+
+ ResourceFileFlattenerOptions file_flattener_options;
+ file_flattener_options.keep_raw_values = keep_raw_values;
+ file_flattener_options.do_not_compress_anything =
+ options_.do_not_compress_anything;
+ file_flattener_options.extensions_to_not_compress =
+ options_.extensions_to_not_compress;
+ file_flattener_options.no_auto_version = options_.no_auto_version;
+ file_flattener_options.no_version_vectors = options_.no_version_vectors;
+ file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
+ file_flattener_options.update_proguard_spec =
+ static_cast<bool>(options_.generate_proguard_rules_path);
+
+ ResourceFileFlattener file_flattener(file_flattener_options, context_,
+ keep_set);
+
+ if (!file_flattener.Flatten(table, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed linking file resources");
+ return false;
+ }
+
+ if (options_.static_lib) {
+ if (!FlattenTableToPb(table, writer)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to write resources.arsc.flat");
+ return false;
+ }
+ } else {
+ if (!FlattenTable(table, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to write resources.arsc");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ int Run(const std::vector<std::string>& input_files) {
+ // Load the AndroidManifest.xml
+ std::unique_ptr<xml::XmlResource> manifest_xml =
+ LoadXml(options_.manifest_path, context_->GetDiagnostics());
+ if (!manifest_xml) {
+ return 1;
+ }
+
+ // First extract the Package name without modifying it (via
+ // --rename-manifest-package).
+ if (Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(
+ manifest_xml.get(), context_->GetDiagnostics())) {
+ const AppInfo& app_info = maybe_app_info.value();
+ context_->SetCompilationPackage(app_info.package);
+ }
+
+ ManifestFixer manifest_fixer(options_.manifest_fixer_options);
+ if (!manifest_fixer.Consume(context_, manifest_xml.get())) {
+ return 1;
+ }
+
+ Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(
+ manifest_xml.get(), context_->GetDiagnostics());
+ if (!maybe_app_info) {
+ return 1;
+ }
+
+ const AppInfo& app_info = maybe_app_info.value();
+ if (app_info.min_sdk_version) {
+ if (Maybe<int> maybe_min_sdk_version = ResourceUtils::ParseSdkVersion(
+ app_info.min_sdk_version.value())) {
+ context_->SetMinSdkVersion(maybe_min_sdk_version.value());
+ }
+ }
+
+ context_->SetNameManglerPolicy(
+ NameManglerPolicy{context_->GetCompilationPackage()});
+ if (context_->GetCompilationPackage() == "android") {
+ context_->SetPackageId(0x01);
+ } else {
+ context_->SetPackageId(0x7f);
+ }
+
+ if (!LoadSymbolsFromIncludePaths()) {
+ return 1;
+ }
+
+ TableMergerOptions table_merger_options;
+ table_merger_options.auto_add_overlay = options_.auto_add_overlay;
+ table_merger_ = util::make_unique<TableMerger>(context_, &final_table_,
+ table_merger_options);
+
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage()
+ << "linking package '"
+ << context_->GetCompilationPackage()
+ << "' with package ID " << std::hex
+ << (int)context_->GetPackageId());
+ }
+
+ for (const std::string& input : input_files) {
+ if (!MergePath(input, false)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed parsing input");
+ return 1;
+ }
+ }
+
+ for (const std::string& input : options_.overlay_files) {
+ if (!MergePath(input, true)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed parsing overlays");
+ return 1;
+ }
+ }
+
+ if (!VerifyNoExternalPackages()) {
+ return 1;
+ }
+
+ if (!options_.static_lib) {
+ PrivateAttributeMover mover;
+ if (!mover.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed moving private attributes");
+ return 1;
+ }
+
+ // Assign IDs if we are building a regular app.
+ IdAssigner id_assigner(&options_.stable_id_map);
+ if (!id_assigner.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed assigning IDs");
+ return 1;
+ }
+
+ // Now grab each ID and emit it as a file.
+ if (options_.resource_id_map_path) {
+ for (auto& package : final_table_.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.
+ options_.stable_id_map[std::move(name)] = ResourceId(
+ package->id.value(), type->id.value(), entry->id.value());
+ }
+ }
+ }
+
+ if (!WriteStableIdMapToPath(context_->GetDiagnostics(),
+ options_.stable_id_map,
+ options_.resource_id_map_path.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.
+ context_->SetNameManglerPolicy(NameManglerPolicy{
+ context_->GetCompilationPackage(), table_merger_->merged_packages()});
+
+ // Add our table to the symbol table.
+ context_->GetExternalSymbols()->PrependSource(
+ util::make_unique<ResourceTableSymbolSource>(&final_table_));
+
+ ReferenceLinker linker;
+ if (!linker.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed linking references");
+ return 1;
+ }
+
+ if (options_.static_lib) {
+ if (!options_.products.empty()) {
+ context_->GetDiagnostics()
+ ->Warn(DiagMessage()
+ << "can't select products when building static library");
+ }
+ } else {
+ ProductFilter product_filter(options_.products);
+ if (!product_filter.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed stripping products");
+ return 1;
+ }
+ }
+
+ if (!options_.no_auto_version) {
+ AutoVersioner versioner;
+ if (!versioner.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed versioning styles");
+ return 1;
+ }
+ }
+
+ if (!options_.static_lib && context_->GetMinSdkVersion() > 0) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "collapsing resource versions for minimum SDK "
+ << context_->GetMinSdkVersion());
+ }
+
+ VersionCollapser collapser;
+ if (!collapser.Consume(context_, &final_table_)) {
+ return 1;
+ }
+ }
+
+ if (!options_.no_resource_deduping) {
+ ResourceDeduper deduper;
+ if (!deduper.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed deduping resources");
+ return 1;
+ }
+ }
+
+ proguard::KeepSet proguard_keep_set;
+ proguard::KeepSet proguard_main_dex_keep_set;
+
+ if (options_.static_lib) {
+ if (options_.table_splitter_options.config_filter != nullptr ||
+ options_.table_splitter_options.preferred_density) {
+ context_->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> adjusted_constraints_list;
+ adjusted_constraints_list.reserve(options_.split_constraints.size());
+ for (const SplitConstraints& constraints : options_.split_constraints) {
+ SplitConstraints adjusted_constraints;
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.sdkVersion <= context_->GetMinSdkVersion()) {
+ adjusted_constraints.configs.insert(config.CopyWithoutSdkVersion());
+ } else {
+ adjusted_constraints.configs.insert(config);
+ }
+ }
+ adjusted_constraints_list.push_back(std::move(adjusted_constraints));
+ }
+
+ TableSplitter table_splitter(adjusted_constraints_list,
+ options_.table_splitter_options);
+ if (!table_splitter.VerifySplitConstraints(context_)) {
+ return 1;
+ }
+ table_splitter.SplitTable(&final_table_);
+
+ // Now we need to write out the Split APKs.
+ auto path_iter = options_.split_paths.begin();
+ auto split_constraints_iter = adjusted_constraints_list.begin();
+ for (std::unique_ptr<ResourceTable>& split_table :
+ table_splitter.splits()) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage(*path_iter)
+ << "generating split with configurations '"
+ << util::Joiner(split_constraints_iter->configs, ", ") << "'");
+ }
+
+ std::unique_ptr<IArchiveWriter> archive_writer =
+ MakeArchiveWriter(*path_iter);
+ if (!archive_writer) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to create archive");
+ return 1;
+ }
+
+ // Generate an AndroidManifest.xml for each split.
+ std::unique_ptr<xml::XmlResource> split_manifest =
+ GenerateSplitManifest(app_info, *split_constraints_iter);
+
+ XmlReferenceLinker linker;
+ if (!linker.Consume(context_, split_manifest.get())) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "failed to create Split AndroidManifest.xml");
+ return 1;
+ }
+
+ if (!WriteApk(archive_writer.get(), &proguard_keep_set,
+ split_manifest.get(), split_table.get())) {
+ return 1;
+ }
+
+ ++path_iter;
+ ++split_constraints_iter;
+ }
+ }
+
+ // Start writing the base APK.
+ std::unique_ptr<IArchiveWriter> archive_writer =
+ MakeArchiveWriter(options_.output_path);
+ if (!archive_writer) {
+ context_->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.
+ manifest_xml->file.name.package = context_->GetCompilationPackage();
+
+ XmlReferenceLinker manifest_linker;
+ if (manifest_linker.Consume(context_, manifest_xml.get())) {
+ if (options_.generate_proguard_rules_path &&
+ !proguard::CollectProguardRulesForManifest(
+ Source(options_.manifest_path), manifest_xml.get(),
+ &proguard_keep_set)) {
+ error = true;
+ }
+
+ if (options_.generate_main_dex_proguard_rules_path &&
+ !proguard::CollectProguardRulesForManifest(
+ Source(options_.manifest_path), manifest_xml.get(),
+ &proguard_main_dex_keep_set, true)) {
+ error = true;
+ }
+
+ if (options_.generate_java_class_path) {
+ if (!WriteManifestJavaFile(manifest_xml.get())) {
+ error = true;
+ }
+ }
+
+ if (options_.no_xml_namespaces) {
+ // PackageParser will fail if URIs are removed from
+ // AndroidManifest.xml.
+ XmlNamespaceRemover namespace_remover(true /* keepUris */);
+ if (!namespace_remover.Consume(context_, manifest_xml.get())) {
+ error = true;
+ }
+ }
+ } else {
+ error = true;
+ }
+ }
+
+ if (error) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed processing manifest");
+ return 1;
+ }
+
+ if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(),
+ &final_table_)) {
+ return 1;
+ }
+
+ if (options_.generate_java_class_path) {
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ options.javadoc_annotations = options_.javadoc_annotations;
+
+ if (options_.static_lib || options_.generate_non_final_ids) {
+ options.use_final = false;
+ }
+
+ const StringPiece actual_package = context_->GetCompilationPackage();
+ StringPiece output_package = context_->GetCompilationPackage();
+ if (options_.custom_java_package) {
+ // Override the output java package to the custom one.
+ output_package = options_.custom_java_package.value();
+ }
+
+ if (options_.private_symbols) {
+ // 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(&final_table_, context_->GetCompilationPackage(),
+ output_package, options)) {
+ return 1;
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ output_package = options_.private_symbols.value();
+ }
+
+ if (!WriteJavaFile(&final_table_, actual_package, output_package,
+ options)) {
+ return 1;
+ }
+
+ for (const std::string& extra_package : options_.extra_java_packages) {
+ if (!WriteJavaFile(&final_table_, actual_package, extra_package,
+ options)) {
+ return 1;
+ }
+ }
+ }
+
+ if (!WriteProguardFile(options_.generate_proguard_rules_path,
+ proguard_keep_set)) {
+ return 1;
+ }
+
+ if (!WriteProguardFile(options_.generate_main_dex_proguard_rules_path,
+ proguard_main_dex_keep_set)) {
+ return 1;
+ }
+
+ if (context_->IsVerbose()) {
+ DebugPrintTableOptions debug_print_table_options;
+ debug_print_table_options.show_sources = true;
+ Debug::PrintTable(&final_table_, debug_print_table_options);
+ }
+ return 0;
+ }
+
+ private:
+ LinkOptions options_;
+ LinkContext* context_;
+ ResourceTable final_table_;
+
+ std::unique_ptr<TableMerger> table_merger_;
+
+ // A pointer to the FileCollection representing the filesystem (not archives).
+ std::unique_ptr<io::FileCollection> file_collection_;
+
+ // A vector of IFileCollections. This is mainly here to keep ownership of the
+ // collections.
+ std::vector<std::unique_ptr<io::IFileCollection>> collections_;
+
+ // A vector of ResourceTables. This is here to retain ownership, so that the
+ // SymbolTable
+ // can use these.
+ std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
};
-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("-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 "
+int Link(const std::vector<StringPiece>& args) {
+ LinkContext context;
+ LinkOptions options;
+ std::vector<std::string> overlay_arg_list;
+ std::vector<std::string> extra_java_packages;
+ Maybe<std::string> configs;
+ Maybe<std::string> preferred_density;
+ Maybe<std::string> product_list;
+ bool legacy_x_flag = false;
+ bool require_localization = false;
+ bool verbose = false;
+ Maybe<std::string> stable_id_file_path;
+ std::vector<std::string> split_args;
+ Flags flags =
+ Flags()
+ .RequiredFlag("-o", "Output path", &options.output_path)
+ .RequiredFlag("--manifest", "Path to the Android manifest to build",
+ &options.manifest_path)
+ .OptionalFlagList("-I", "Adds an Android APK to link against",
+ &options.include_paths)
+ .OptionalFlagList(
+ "-R",
+ "Compilation unit to link, using `overlay` semantics.\n"
+ "The last conflicting resource given takes precedence.",
+ &overlay_arg_list)
+ .OptionalFlag("--java", "Directory in which to generate R.java",
+ &options.generate_java_class_path)
+ .OptionalFlag("--proguard",
+ "Output file for generated Proguard rules",
+ &options.generate_proguard_rules_path)
+ .OptionalFlag(
+ "--proguard-main-dex",
+ "Output file for generated Proguard rules for the main dex",
+ &options.generate_main_dex_proguard_rules_path)
+ .OptionalSwitch("--no-auto-version",
+ "Disables automatic style and layout SDK versioning",
+ &options.no_auto_version)
+ .OptionalSwitch("--no-version-vectors",
+ "Disables automatic versioning of vector drawables. "
+ "Use this only\n"
+ "when building with vector drawable support library",
+ &options.no_version_vectors)
+ .OptionalSwitch("--no-resource-deduping",
+ "Disables automatic deduping of resources with\n"
+ "identical values across compatible configurations.",
+ &options.no_resource_deduping)
+ .OptionalSwitch(
+ "-x",
+ "Legacy flag that specifies to use the package identifier 0x01",
+ &legacy_x_flag)
+ .OptionalSwitch("-z",
+ "Require localization of strings marked 'suggested'",
+ &require_localization)
+ .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.",
+ &preferred_density)
+ .OptionalFlag("--product",
+ "Comma separated list of product names to keep",
+ &product_list)
+ .OptionalSwitch("--output-to-dir",
+ "Outputs the APK contents to a directory specified "
+ "by -o",
+ &options.output_to_directory)
+ .OptionalSwitch("--no-xml-namespaces",
+ "Removes XML namespace prefix and URI "
+ "information from AndroidManifest.xml\nand XML "
+ "binaries in res/*.",
+ &options.no_xml_namespaces)
+ .OptionalFlag("--min-sdk-version",
+ "Default minimum SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifest_fixer_options.min_sdk_version_default)
+ .OptionalFlag(
+ "--target-sdk-version",
+ "Default target SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifest_fixer_options.target_sdk_version_default)
+ .OptionalFlag("--version-code",
+ "Version code (integer) to inject into the "
+ "AndroidManifest.xml if none is present",
+ &options.manifest_fixer_options.version_code_default)
+ .OptionalFlag("--version-name",
+ "Version name to inject into the AndroidManifest.xml "
+ "if none is present",
+ &options.manifest_fixer_options.version_name_default)
+ .OptionalSwitch("--static-lib", "Generate a static Android library",
+ &options.static_lib)
+ .OptionalSwitch("--no-static-lib-packages",
+ "Merge all library resources under the app's package",
+ &options.no_static_lib_packages)
+ .OptionalSwitch("--non-final-ids",
+ "Generates R.java without the final modifier.\n"
+ "This is implied when --static-lib is specified.",
+ &options.generate_non_final_ids)
+ .OptionalFlag("--stable-ids",
+ "File containing a list of name to ID mapping.",
+ &stable_id_file_path)
+ .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.resource_id_map_path)
+ .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.private_symbols)
+ .OptionalFlag("--custom-package",
+ "Custom Java package under which to generate R.java",
+ &options.custom_java_package)
+ .OptionalFlagList("--extra-packages",
+ "Generate the same R.java but with different "
+ "package names",
+ &extra_java_packages)
+ .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);
+ &options.javadoc_annotations)
+ .OptionalSwitch("--auto-add-overlay",
+ "Allows the addition of new resources in "
+ "overlays without <add-resource> tags",
+ &options.auto_add_overlay)
+ .OptionalFlag("--rename-manifest-package",
+ "Renames the package in AndroidManifest.xml",
+ &options.manifest_fixer_options.rename_manifest_package)
+ .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.manifest_fixer_options
+ .rename_instrumentation_target_package)
+ .OptionalFlagList("-0", "File extensions not to compress",
+ &options.extensions_to_not_compress)
+ .OptionalFlagList(
+ "--split",
+ "Split resources matching a set of configs out to a "
+ "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
+ &split_args)
+ .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> arg_list;
+ for (const std::string& arg : flags.GetArgs()) {
+ if (util::StartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::AppendArgsFromFile(path, &arg_list, &error)) {
+ context.GetDiagnostics()->Error(DiagMessage(path) << error);
return 1;
+ }
+ } else {
+ arg_list.push_back(arg);
+ }
+ }
+
+ // Expand all argument-files passed to -R.
+ for (const std::string& arg : overlay_arg_list) {
+ if (util::StartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::AppendArgsFromFile(path, &options.overlay_files, &error)) {
+ context.GetDiagnostics()->Error(DiagMessage(path) << error);
+ return 1;
+ }
+ } else {
+ options.overlay_files.push_back(arg);
+ }
+ }
+
+ if (verbose) {
+ context.SetVerbose(verbose);
+ }
+
+ // Populate the set of extra packages for which to generate R.java.
+ for (std::string& extra_package : extra_java_packages) {
+ // A given package can actually be a colon separated list of packages.
+ for (StringPiece package : util::Split(extra_package, ':')) {
+ options.extra_java_packages.insert(package.ToString());
+ }
+ }
+
+ if (product_list) {
+ for (StringPiece product : util::Tokenize(product_list.value(), ',')) {
+ if (product != "" && product != "default") {
+ options.products.insert(product.ToString());
+ }
+ }
+ }
+
+ AxisConfigFilter filter;
+ if (configs) {
+ for (const StringPiece& config_str : util::Tokenize(configs.value(), ',')) {
+ ConfigDescription config;
+ LocaleValue lv;
+ if (lv.InitFromFilterString(config_str)) {
+ lv.WriteTo(&config);
+ } else if (!ConfigDescription::Parse(config_str, &config)) {
+ context.GetDiagnostics()->Error(DiagMessage() << "invalid config '"
+ << config_str
+ << "' 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.table_splitter_options.config_filter = &filter;
+ }
+
+ if (preferred_density) {
+ ConfigDescription preferred_density_config;
+ if (!ConfigDescription::Parse(preferred_density.value(),
+ &preferred_density_config)) {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "invalid density '" << preferred_density.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.
+ preferred_density_config.sdkVersion = 0;
+
+ if (preferred_density_config.diff(ConfigDescription::DefaultConfig()) !=
+ ConfigDescription::CONFIG_DENSITY) {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "invalid preferred density '"
+ << preferred_density.value() << "'. "
+ << "Preferred density must only be a density value");
+ return 1;
}
+ options.table_splitter_options.preferred_density =
+ preferred_density_config.density;
+ }
- if (verbose) {
- context.setVerbose(verbose);
+ if (!options.static_lib && stable_id_file_path) {
+ if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(),
+ &options.stable_id_map)) {
+ 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.extensions_to_not_compress.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& split_arg : split_args) {
+ options.split_paths.push_back({});
+ options.split_constraints.push_back({});
+ if (!ParseSplitParameter(split_arg, context.GetDiagnostics(),
+ &options.split_paths.back(),
+ &options.split_constraints.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.static_lib) {
+ options.no_auto_version = true;
+ options.no_version_vectors = 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(arg_list);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 82e2868..4687d2c 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -17,12 +17,15 @@
#ifndef AAPT_LINKER_LINKERS_H
#define AAPT_LINKER_LINKERS_H
+#include <set>
+#include <unordered_set>
+
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
-#include <set>
-
namespace aapt {
class ResourceTable;
@@ -30,98 +33,174 @@
struct ConfigDescription;
/**
- * Defines the location in which a value exists. This determines visibility of other
- * package's private symbols.
+ * 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
- * exists, it takes precedence.
+ * 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,
- const int sdkVersionToGenerate);
+bool ShouldGenerateVersionedResource(const ResourceEntry* entry,
+ const ConfigDescription& config,
+ const int sdk_version_to_generate);
class AutoVersioner : public IResourceTableConsumer {
-public:
- bool consume(IAaptContext* context, ResourceTable* table) override;
-};
+ public:
+ AutoVersioner() = default;
-class XmlAutoVersioner : public IXmlResourceConsumer {
-public:
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutoVersioner);
};
class VersionCollapser : public IResourceTableConsumer {
-public:
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ public:
+ VersionCollapser() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VersionCollapser);
};
/**
- * 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
+ * Removes duplicated key-value entries from dominated resources.
+ */
+class ResourceDeduper : public IResourceTableConsumer {
+ public:
+ ResourceDeduper() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceDeduper);
+};
+
+/**
+ * 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;
+class PrivateAttributeMover : public IResourceTableConsumer {
+ public:
+ PrivateAttributeMover() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PrivateAttributeMover);
+};
+
+class ResourceConfigValue;
+
+class ProductFilter : public IResourceTableConsumer {
+ public:
+ using ResourceConfigValueIter =
+ std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+
+ explicit ProductFilter(std::unordered_set<std::string> products)
+ : products_(products) {}
+
+ ResourceConfigValueIter SelectProductToKeep(
+ const ResourceNameRef& name, const ResourceConfigValueIter begin,
+ const ResourceConfigValueIter end, IDiagnostics* diag);
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ std::unordered_set<std::string> products_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+};
+
+class XmlAutoVersioner : public IXmlResourceConsumer {
+ public:
+ XmlAutoVersioner() = default;
+
+ bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlAutoVersioner);
};
/**
* 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;
+ public:
+ explicit XmlNamespaceRemover(bool keep_uris = false)
+ : keep_uris_(keep_uris){};
-public:
- XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris) {
- };
+ bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlNamespaceRemover);
+
+ bool keep_uris_;
};
/**
- * 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;
+ public:
+ XmlReferenceLinker() = default;
-public:
- bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+ 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>& sdk_levels() const { return sdk_levels_found_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker);
+
+ std::set<int> sdk_levels_found_;
};
-} // 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..a418fc8 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -14,306 +14,326 @@
* limitations under the License.
*/
-#include "ResourceUtils.h"
#include "link/ManifestFixer.h"
+
+#include <unordered_set>
+
+#include "android-base/logging.h"
+
+#include "ResourceUtils.h"
#include "util/Util.h"
#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
-#include <unordered_set>
-
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,
+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> fully_qualified_class_name =
+ util::GetFullyQualifiedClassName("a", attr->value);
- StringPiece qualifiedClassName = fullyQualifiedClassName
- ? fullyQualifiedClassName.value() : attr->value;
+ StringPiece qualified_class_name = fully_qualified_class_name
+ ? fully_qualified_class_name.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);
- }
- diag->error(DiagMessage(el->lineNumber)
- << "<" << el->name << "> is missing attribute 'android:name'");
+ if (!util::IsJavaClassName(qualified_class_name)) {
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute 'android:name' in <" << el->name
+ << "> tag must be a valid Java class name");
return false;
+ }
+ return true;
}
-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;
+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->line_number)
+ << "<" << 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->line_number)
+ << "<manifest> tag is missing 'package' attribute");
+ return false;
+ } else if (ResourceUtils::IsReference(attr->value)) {
+ diag->Error(
+ DiagMessage(el->line_number)
+ << "attribute 'package' in <manifest> tag must not be a reference");
+ return false;
+ } else if (!util::IsJavaPackageName(attr->value)) {
+ diag->Error(DiagMessage(el->line_number)
+ << "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
- * is manual.
+ * 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);
+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->line_number)
+ << "attribute coreApp must be a boolean");
+ return false;
}
- return true;
+ attr->compiled_value = 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 (options_.rename_manifest_package) {
+ if (!util::IsJavaPackageName(options_.rename_manifest_package.value())) {
+ diag->Error(DiagMessage() << "invalid manifest package override '"
+ << options_.rename_manifest_package.value()
+ << "'");
+ return false;
+ }
+ }
+
+ if (options_.rename_instrumentation_target_package) {
+ if (!util::IsJavaPackageName(
+ options_.rename_instrumentation_target_package.value())) {
+ diag->Error(DiagMessage()
+ << "invalid instrumentation target package override '"
+ << options_.rename_instrumentation_target_package.value()
+ << "'");
+ return false;
+ }
+ }
+
+ // Common intent-filter actions.
+ xml::XmlNodeAction intent_filter_action;
+ intent_filter_action["action"];
+ intent_filter_action["category"];
+ intent_filter_action["data"];
+
+ // Common meta-data actions.
+ xml::XmlNodeAction meta_data_action;
+
+ // Manifest actions.
+ xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
+ manifest_action.Action(VerifyManifest);
+ manifest_action.Action(FixCoreAppAttribute);
+ manifest_action.Action([&](xml::Element* el) -> bool {
+ if (options_.version_name_default) {
+ if (el->FindAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionName",
+ options_.version_name_default.value()});
+ }
}
- if (mOptions.renameInstrumentationTargetPackage) {
- if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) {
- diag->error(DiagMessage() << "invalid instrumentation target package override '"
- << mOptions.renameInstrumentationTargetPackage.value() << "'");
- return false;
- }
+ if (options_.version_code_default) {
+ if (el->FindAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCode",
+ options_.version_code_default.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.
+ manifest_action["eat-comment"];
+
+ // Uses-sdk actions.
+ manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool {
+ if (options_.min_sdk_version_default &&
+ 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",
+ options_.min_sdk_version_default.value()});
+ }
+
+ if (options_.target_sdk_version_default &&
+ 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",
+ options_.target_sdk_version_default.value()});
+ }
+ return true;
+ });
+
+ // Instrumentation actions.
+ manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool {
+ if (!options_.rename_instrumentation_target_package) {
+ return true;
+ }
+
+ if (xml::Attribute* attr =
+ el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
+ attr->value = options_.rename_instrumentation_target_package.value();
+ }
+ return true;
+ });
+
+ manifest_action["original-package"];
+ manifest_action["protected-broadcast"];
+ manifest_action["uses-permission"];
+ manifest_action["permission"];
+ manifest_action["permission-tree"];
+ manifest_action["permission-group"];
+
+ manifest_action["uses-configuration"];
+ manifest_action["uses-feature"];
+ manifest_action["supports-screens"];
+
+ manifest_action["compatible-screens"];
+ manifest_action["compatible-screens"]["screen"];
+
+ manifest_action["supports-gl-texture"];
+
+ // Application actions.
+ xml::XmlNodeAction& application_action = manifest_action["application"];
+ application_action.Action(OptionalNameIsJavaClassName);
+
+ // Uses library actions.
+ application_action["uses-library"];
+
+ // Meta-data.
+ application_action["meta-data"] = meta_data_action;
+
+ // Activity actions.
+ application_action["activity"].Action(RequiredNameIsJavaClassName);
+ application_action["activity"]["intent-filter"] = intent_filter_action;
+ application_action["activity"]["meta-data"] = meta_data_action;
+
+ // Activity alias actions.
+ application_action["activity-alias"]["intent-filter"] = intent_filter_action;
+ application_action["activity-alias"]["meta-data"] = meta_data_action;
+
+ // Service actions.
+ application_action["service"].Action(RequiredNameIsJavaClassName);
+ application_action["service"]["intent-filter"] = intent_filter_action;
+ application_action["service"]["meta-data"] = meta_data_action;
+
+ // Receiver actions.
+ application_action["receiver"].Action(RequiredNameIsJavaClassName);
+ application_action["receiver"]["intent-filter"] = intent_filter_action;
+ application_action["receiver"]["meta-data"] = meta_data_action;
+
+ // Provider actions.
+ application_action["provider"].Action(RequiredNameIsJavaClassName);
+ application_action["provider"]["intent-filter"] = intent_filter_action;
+ application_action["provider"]["meta-data"] = meta_data_action;
+ application_action["provider"]["grant-uri-permissions"];
+ application_action["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)
+ : package_(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.namespace_uri == xml::kSchemaAndroid &&
+ class_attributes_.find(attr.name) != class_attributes_.end()) {
+ if (Maybe<std::string> new_value =
+ util::GetFullyQualifiedClassName(package_, attr.value)) {
+ attr.value = std::move(new_value.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 package_;
+ std::unordered_set<StringPiece> class_attributes_ = {"name"};
};
-static bool renameManifestPackage(const StringPiece& packageOverride, xml::Element* manifestEl) {
- xml::Attribute* attr = manifestEl->findAttribute({}, "package");
+static bool RenameManifestPackage(const StringPiece& package_override,
+ xml::Element* manifest_el) {
+ xml::Attribute* attr = manifest_el->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.
+ CHECK(attr != nullptr);
- std::string originalPackage = std::move(attr->value);
- attr->value = packageOverride.toString();
+ std::string original_package = std::move(attr->value);
+ attr->value = package_override.ToString();
- FullyQualifiedClassNameVisitor visitor(originalPackage);
- manifestEl->accept(&visitor);
- return true;
+ FullyQualifiedClassNameVisitor visitor(original_package);
+ manifest_el->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;
- }
+bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) {
+ xml::Element* root = xml::FindRootElement(doc->root.get());
+ if (!root || !root->namespace_uri.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 ((options_.min_sdk_version_default ||
+ options_.target_sdk_version_default) &&
+ root->FindChild({}, "uses-sdk") == nullptr) {
+ // Auto insert a <uses-sdk> element.
+ std::unique_ptr<xml::Element> uses_sdk = util::make_unique<xml::Element>();
+ uses_sdk->name = "uses-sdk";
+ root->AddChild(std::move(uses_sdk));
+ }
- 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::kWhitelist,
+ 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 (options_.rename_manifest_package) {
+ // Rename manifest package outside of the XmlActionExecutor.
+ // We need to extract the old package name and FullyQualify all class names.
+ if (!RenameManifestPackage(options_.rename_manifest_package.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..470f65e 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -17,22 +17,24 @@
#ifndef AAPT_LINK_MANIFESTFIXER_H
#define AAPT_LINK_MANIFESTFIXER_H
+#include <string>
+
+#include "android-base/macros.h"
+
#include "process/IResourceTableConsumer.h"
#include "util/Maybe.h"
#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
-#include <string>
-
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> min_sdk_version_default;
+ Maybe<std::string> target_sdk_version_default;
+ Maybe<std::string> rename_manifest_package;
+ Maybe<std::string> rename_instrumentation_target_package;
+ Maybe<std::string> version_name_default;
+ Maybe<std::string> version_code_default;
};
/**
@@ -40,18 +42,20 @@
* where specified with ManifestFixerOptions.
*/
class ManifestFixer : public IXmlResourceConsumer {
-public:
- explicit ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
- }
+ public:
+ explicit ManifestFixer(const ManifestFixerOptions& options)
+ : options_(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:
+ DISALLOW_COPY_AND_ASSIGN(ManifestFixer);
- ManifestFixerOptions mOptions;
+ bool BuildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
+
+ ManifestFixerOptions options_;
};
-} // 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..0e29ba6 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -15,262 +15,280 @@
*/
#include "link/ManifestFixer.h"
-#include "test/Builders.h"
-#include "test/Context.h"
-#include <gtest/gtest.h>
+#include "test/Test.h"
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.rename_manifest_package = 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.rename_instrumentation_target_package = 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* manifest_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, manifest_el);
- xml::Element* instrumentationEl = manifestEl->findChild({}, "instrumentation");
- ASSERT_NE(nullptr, instrumentationEl);
+ xml::Element* instrumentation_el =
+ manifest_el->FindChild({}, "instrumentation");
+ ASSERT_NE(nullptr, instrumentation_el);
- xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("com.android"), attr->value);
+ xml::Attribute* attr =
+ instrumentation_el->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.version_name_default = std::string("Beta");
+ options.version_code_default = 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* manifest_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(nullptr, manifest_el);
- xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionName");
- ASSERT_NE(nullptr, attr);
- EXPECT_EQ(std::string("Beta"), attr->value);
+ xml::Attribute* attr =
+ manifest_el->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 = manifest_el->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->compiled_value);
+ EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(attr->compiled_value.get()));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 3c8af4f..cc07a6e 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -14,67 +14,74 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "link/Linkers.h"
#include <algorithm>
#include <iterator>
+#include "android-base/logging.h"
+
+#include "ResourceTable.h"
+
namespace aapt {
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);
+OutputIterator move_if(InputContainer& input_container, OutputIterator result,
+ Predicate pred) {
+ const auto last = input_container.end();
+ auto new_end =
+ std::find_if(input_container.begin(), input_container.end(), pred);
+ if (new_end == 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(*new_end);
- if (type->symbolStatus.state != SymbolState::kPublic) {
- // No public attributes, so we can safely leave these private attributes where they are.
- return true;
- }
+ auto first = new_end;
+ ++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.
+ *new_end = std::move(*first);
+ ++new_end;
}
- return true;
+ }
+
+ input_container.erase(new_end, 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->symbol_status.state != SymbolState::kPublic) {
+ // No public attributes, so we can safely leave these private attributes
+ // where they are.
+ return true;
+ }
+
+ ResourceTableType* priv_attr_type =
+ package->FindOrCreateType(ResourceType::kAttrPrivate);
+ CHECK(priv_attr_type->entries.empty());
+
+ move_if(type->entries, std::back_inserter(priv_attr_type->entries),
+ [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+ return entry->symbol_status.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..90c4922 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -15,61 +15,66 @@
*/
#include "link/Linkers.h"
+
#include "test/Test.h"
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..c1a95ee1 100644
--- a/tools/aapt2/link/ProductFilter.cpp
+++ b/tools/aapt2/link/ProductFilter.cpp
@@ -14,105 +14,110 @@
* limitations under the License.
*/
-#include "link/ProductFilter.h"
+#include "link/Linkers.h"
+
+#include "ResourceTable.h"
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 default_product_iter = end;
+ ResourceConfigValueIter selected_product_iter = 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* config_value = iter->get();
+ if (products_.find(config_value->product) != products_.end()) {
+ if (selected_product_iter != end) {
+ // We have two possible values for this product!
+ diag->Error(DiagMessage(config_value->value->GetSource())
+ << "selection of product '" << config_value->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* previously_selected_config_value =
+ selected_product_iter->get();
+ diag->Note(
+ DiagMessage(previously_selected_config_value->value->GetSource())
+ << "product '" << previously_selected_config_value->product
+ << "' is also a candidate");
return end;
+ }
+
+ // Select this product.
+ selected_product_iter = iter;
}
- if (selectedProductIter == end) {
- selectedProductIter = defaultProductIter;
+ if (config_value->product.empty() || config_value->product == "default") {
+ if (default_product_iter != end) {
+ // We have two possible default values.
+ diag->Error(DiagMessage(config_value->value->GetSource())
+ << "multiple default products defined for resource "
+ << name);
+
+ ResourceConfigValue* previously_default_config_value =
+ default_product_iter->get();
+ diag->Note(
+ DiagMessage(previously_default_config_value->value->GetSource())
+ << "default product also defined here");
+ return end;
+ }
+
+ // Mark the default.
+ default_product_iter = iter;
}
- return selectedProductIter;
+ }
+
+ if (default_product_iter == end) {
+ diag->Error(DiagMessage() << "no default product defined for resource "
+ << name);
+ return end;
+ }
+
+ if (selected_product_iter == end) {
+ selected_product_iter = default_product_iter;
+ }
+ return selected_product_iter;
}
-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 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>> new_values;
- 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 start_range_iter = iter;
+ while (iter != entry->values.end()) {
+ ++iter;
+ if (iter == entry->values.end() ||
+ (*iter)->config != (*start_range_iter)->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 value_to_keep = SelectProductToKeep(
+ name, start_range_iter, iter, context->GetDiagnostics());
+ if (value_to_keep == 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.
+ new_values.push_back(std::move(*value_to_keep));
}
+
+ // Start the next range of products.
+ start_range_iter = iter;
+ }
}
+
+ // Now move the new values in to place.
+ entry->values = std::move(new_values);
+ }
}
- return !error;
+ }
+ return !error;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h
deleted file mode 100644
index 7724e14..0000000
--- a/tools/aapt2/link/ProductFilter.h
+++ /dev/null
@@ -1,49 +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.
- */
-
-#ifndef AAPT_LINK_PRODUCTFILTER_H
-#define AAPT_LINK_PRODUCTFILTER_H
-
-#include "ResourceTable.h"
-#include "process/IResourceTableConsumer.h"
-
-#include <android-base/macros.h>
-#include <unordered_set>
-
-namespace aapt {
-
-class ProductFilter {
-public:
- using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
-
- explicit ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { }
-
- ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name,
- const ResourceConfigValueIter begin,
- const ResourceConfigValueIter end,
- IDiagnostics* diag);
-
- bool consume(IAaptContext* context, ResourceTable* table);
-
-private:
- std::unordered_set<std::string> mProducts;
-
- DISALLOW_COPY_AND_ASSIGN(ProductFilter);
-};
-
-} // 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..379ad26 100644
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -14,120 +14,117 @@
* limitations under the License.
*/
-#include "link/ProductFilter.h"
+#include "link/Linkers.h"
+
#include "test/Test.h"
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..be787b2 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,8 +14,12 @@
* limitations under the License.
*/
+#include "link/ReferenceLinker.h"
+
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
#include "Diagnostics.h"
-#include "ReferenceLinker.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
@@ -26,310 +30,344 @@
#include "util/Util.h"
#include "xml/XmlUtil.h"
-#include <androidfw/ResourceTypes.h>
-#include <cassert>
-
namespace aapt {
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* string_pool, xml::IPackageDeclStack* decl,
+ CallSite* callsite)
+ : context_(context),
+ symbols_(symbols),
+ package_decls_(decl),
+ string_pool_(string_pool),
+ callsite_(callsite) {}
+
+ void Visit(Reference* ref) override {
+ if (!ReferenceLinker::LinkReference(ref, context_, symbols_, package_decls_,
+ callsite_)) {
+ error_ = 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 err_str;
- /**
- * 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 transformed_reference = entry.key;
+ TransformReferenceFromNamespace(package_decls_,
+ context_->GetCompilationPackage(),
+ &transformed_reference);
+
+ // Find the attribute in the symbol table and check if it is visible from
+ // this callsite.
+ const SymbolTable::Symbol* symbol =
+ ReferenceLinker::ResolveAttributeCheckVisibility(
+ transformed_reference, context_->GetNameMangler(), symbols_,
+ callsite_, &err_str);
+ 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);
+ context_->GetDiagnostics()->Error(msg);
+ error_ = true;
}
- for (Style::Entry& entry : style->entries) {
- std::string errStr;
+ } else {
+ DiagMessage msg(entry.key.GetSource());
+ msg << "style attribute '";
+ ReferenceLinker::WriteResourceName(&msg, entry.key,
+ transformed_reference);
+ msg << "' " << err_str;
+ context_->GetDiagnostics()->Error(msg);
+ error_ = 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 error_; }
- // 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:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor);
- // 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* raw_string = ValueCast<RawString>(value.get())) {
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::TryParseItemForAttribute(*raw_string->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->type_mask & android::ResTable_map::TYPE_STRING)) {
+ util::StringBuilder string_builder;
+ string_builder.Append(*raw_string->value);
+ if (string_builder) {
+ transformed = util::make_unique<String>(
+ string_pool_->MakeRef(string_builder.ToString()));
}
- }
+ }
- bool hasError() {
- return mError;
- }
+ if (transformed) {
+ return transformed;
+ }
+ };
+ return value;
+ }
-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;
- }
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ xml::IPackageDeclStack* package_decls_;
+ StringPool* string_pool_;
+ CallSite* callsite_;
+ bool error_ = false;
};
-} // namespace
+class EmptyDeclStack : public xml::IPackageDeclStack {
+ public:
+ EmptyDeclStack() = default;
+
+ Maybe<xml::ExtractedPackage> TransformPackageAlias(
+ const StringPiece& alias,
+ const StringPiece& local_package) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{local_package.ToString(),
+ true /* private */};
+ }
+ return {};
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack);
+};
+
+} // 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,
- 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;
- }
- }
- 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());
+bool ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol,
+ const Reference& ref,
+ const CallSite& callsite) {
+ if (!symbol.is_public && !ref.private_reference) {
+ if (ref.name) {
+ return callsite.resource.package == ref.name.value().package;
+ } else if (ref.id && symbol.id) {
+ return ref.id.value().package_id() == symbol.id.value().package_id();
} else {
- return nullptr;
+ return false;
}
+ }
+ return true;
}
-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;
- }
-
- if (!isSymbolVisible(*symbol, reference, *callSite)) {
- if (outError) *outError = "is private";
- return nullptr;
- }
- return symbol;
+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::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 SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
+ CallSite* callsite, std::string* out_error) {
+ const SymbolTable::Symbol* symbol =
+ ResolveSymbol(reference, name_mangler, symbols);
+ if (!symbol) {
+ if (out_error) *out_error = "not found";
+ return nullptr;
+ }
- if (!symbol->attribute) {
- if (outError) *outError = "is not an attribute";
- return nullptr;
- }
- return symbol;
+ if (!IsSymbolVisible(*symbol, reference, *callsite)) {
+ if (out_error) *out_error = "is private";
+ 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 {};
- }
+const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
+ const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
+ CallSite* callsite, std::string* out_error) {
+ const SymbolTable::Symbol* symbol = ResolveSymbolCheckVisibility(
+ reference, name_mangler, symbols, callsite, out_error);
+ if (!symbol) {
+ return nullptr;
+ }
- if (!symbol->attribute) {
- if (outError) *outError = "is not an attribute";
- return {};
- }
- return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+ if (!symbol->attribute) {
+ if (out_error) *out_error = "is not an attribute";
+ return nullptr;
+ }
+ return symbol;
}
-void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(
+ const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
+ CallSite* callsite, std::string* out_error) {
+ const SymbolTable::Symbol* symbol =
+ ResolveSymbol(reference, name_mangler, symbols);
+ if (!symbol) {
+ if (out_error) *out_error = "not found";
+ return {};
+ }
+
+ if (!symbol->attribute) {
+ if (out_error) *out_error = "is not an attribute";
+ return {};
+ }
+ return xml::AaptAttribute{symbol->id, *symbol->attribute};
+}
+
+void ReferenceLinker::WriteResourceName(DiagMessage* out_msg,
+ const Reference& orig,
const Reference& transformed) {
- assert(outMsg);
+ CHECK(out_msg != nullptr);
- 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) {
+ *out_msg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *out_msg << " (aka " << transformed.name.value() << ")";
}
+ } else {
+ *out_msg << orig.id.value();
+ }
}
-bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
- SymbolTable* symbols, xml::IPackageDeclStack* decls,
- CallSite* callSite) {
- assert(reference);
- assert(reference->name || reference->id);
+bool ReferenceLinker::LinkReference(Reference* reference, IAaptContext* context,
+ SymbolTable* symbols,
+ xml::IPackageDeclStack* decls,
+ CallSite* callsite) {
+ CHECK(reference != nullptr);
+ CHECK(reference->name || reference->id);
- Reference transformedReference = *reference;
- transformReferenceFromNamespace(decls, context->getCompilationPackage(),
- &transformedReference);
+ Reference transformed_reference = *reference;
+ TransformReferenceFromNamespace(decls, context->GetCompilationPackage(),
+ &transformed_reference);
- 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 err_str;
+ const SymbolTable::Symbol* s = ResolveSymbolCheckVisibility(
+ transformed_reference, context->GetNameMangler(), symbols, callsite,
+ &err_str);
+ 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 error_msg(reference->GetSource());
+ error_msg << "resource ";
+ WriteResourceName(&error_msg, *reference, transformed_reference);
+ error_msg << " " << err_str;
+ context->GetDiagnostics()->Error(error_msg);
+ 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 */ };
+bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
+ EmptyDeclStack decl_stack;
+ 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->symbol_status.state != SymbolState::kUndefined &&
+ entry->values.empty()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(entry->symbol_status.source)
+ << "no definition for declared symbol '"
+ << ResourceNameRef(package->name, type->type, entry->name)
+ << "'");
+ error = true;
}
- return {};
- }
-};
-} // namespace
+ CallSite callsite = {
+ ResourceNameRef(package->name, type->type, entry->name)};
+ ReferenceLinkerVisitor visitor(context, context->GetExternalSymbols(),
+ &table->string_pool, &decl_stack,
+ &callsite);
-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;
- }
- }
+ for (auto& config_value : entry->values) {
+ config_value->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..bdabf24 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -17,6 +17,8 @@
#ifndef AAPT_LINKER_REFERENCELINKER_H
#define AAPT_LINKER_REFERENCELINKER_H
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
@@ -25,82 +27,91 @@
#include "process/SymbolTable.h"
#include "xml/XmlDom.h"
-#include <cassert>
-
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);
+class ReferenceLinker : public IResourceTableConsumer {
+ public:
+ ReferenceLinker() = default;
- /**
- * 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);
+ /**
+ * 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. 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.
+ * Returns nullptr if the symbol was not found.
+ */
+ static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference,
+ NameMangler* mangler,
+ SymbolTable* symbols);
- /**
- * 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);
+ /**
+ * 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. out_error holds the error message.
+ */
+ static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* name_mangler,
+ SymbolTable* symbols, CallSite* callsite, std::string* out_error);
- /**
- * 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);
+ /**
+ * 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* name_mangler,
+ SymbolTable* symbols, CallSite* callsite, std::string* out_error);
- /**
- * 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);
+ /**
+ * 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* name_mangler,
+ SymbolTable* symbols, CallSite* callsite, std::string* out_error);
- /**
- * 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);
+ /**
+ * Writes the resource name to the DiagMessage, using the
+ * "orig_name (aka <transformed_name>)" syntax.
+ */
+ static void WriteResourceName(DiagMessage* out_msg, const Reference& orig,
+ const Reference& transformed);
- /**
- * Links all references in the ResourceTable.
- */
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ /**
+ * 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;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceLinker);
};
-} // 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..4ca36a9 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -15,6 +15,7 @@
*/
#include "link/ReferenceLinker.h"
+
#include "test/Test.h"
using android::ResTable_map;
@@ -22,206 +23,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->string_pool.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
new file mode 100644
index 0000000..9431dce
--- /dev/null
+++ b/tools/aapt2/link/ResourceDeduper.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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 "link/Linkers.h"
+
+#include <algorithm>
+
+#include "DominatorTree.h"
+#include "ResourceTable.h"
+
+namespace aapt {
+
+namespace {
+
+/**
+ * Remove duplicated key-value entries from dominated resources.
+ *
+ * Based on the dominator tree, we can remove a value of an entry if:
+ *
+ * 1. The configuration for the entry's value is dominated by a configuration
+ * with an equivalent entry value.
+ * 2. All compatible configurations for the entry (those not in conflict and
+ * unrelated by domination with the configuration for the entry's value) have
+ * an equivalent entry value.
+ */
+class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor {
+ public:
+ using Node = DominatorTree::Node;
+
+ explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry)
+ : context_(context), entry_(entry) {}
+
+ void VisitConfig(Node* node) {
+ Node* parent = node->parent();
+ if (!parent) {
+ return;
+ }
+ ResourceConfigValue* node_value = node->value();
+ ResourceConfigValue* parent_value = parent->value();
+ if (!node_value || !parent_value) {
+ return;
+ }
+ if (!node_value->value->Equals(parent_value->value.get())) {
+ return;
+ }
+
+ // Compare compatible configs for this entry and ensure the values are
+ // equivalent.
+ const ConfigDescription& node_configuration = node_value->config;
+ for (const auto& sibling : entry_->values) {
+ if (!sibling->value) {
+ // Sibling was already removed.
+ continue;
+ }
+ if (node_configuration.IsCompatibleWith(sibling->config) &&
+ !node_value->value->Equals(sibling->value.get())) {
+ // The configurations are compatible, but the value is
+ // different, so we can't remove this value.
+ return;
+ }
+ }
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage(node_value->value->GetSource())
+ << "removing dominated duplicate resource with name \""
+ << entry_->name << "\"");
+ }
+ node_value->value = {};
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DominatedKeyValueRemover);
+
+ IAaptContext* context_;
+ ResourceEntry* entry_;
+};
+
+static void DedupeEntry(IAaptContext* context, ResourceEntry* entry) {
+ 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());
+}
+
+} // 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());
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ResourceDeduper_test.cpp b/tools/aapt2/link/ResourceDeduper_test.cpp
new file mode 100644
index 0000000..d38059d
--- /dev/null
+++ b/tools/aapt2/link/ResourceDeduper_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "link/Linkers.h"
+
+#include "ResourceTable.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(ResourceDeduperTest, SameValuesAreDeduped) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription en_config = test::ParseConfigOrDie("en");
+ const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
+ // Chosen because this configuration is compatible with en.
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/dedupe", ResourceId{}, default_config,
+ "dedupe")
+ .AddString("android:string/dedupe", ResourceId{}, en_config, "dedupe")
+ .AddString("android:string/dedupe", ResourceId{}, land_config,
+ "dedupe")
+ .AddString("android:string/dedupe2", ResourceId{}, default_config,
+ "dedupe")
+ .AddString("android:string/dedupe2", ResourceId{}, en_config,
+ "dedupe")
+ .AddString("android:string/dedupe2", ResourceId{}, en_v21_config,
+ "keep")
+ .AddString("android:string/dedupe2", ResourceId{}, land_config,
+ "dedupe")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe", en_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe", land_config));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe2", en_config));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/dedupe2", en_v21_config));
+}
+
+TEST(ResourceDeduperTest, DifferentValuesAreKept) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription en_config = test::ParseConfigOrDie("en");
+ const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
+ // Chosen because this configuration is compatible with en.
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/keep", ResourceId{}, default_config,
+ "keep")
+ .AddString("android:string/keep", ResourceId{}, en_config, "keep")
+ .AddString("android:string/keep", ResourceId{}, en_v21_config,
+ "keep2")
+ .AddString("android:string/keep", ResourceId{}, land_config, "keep2")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/keep", en_config));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/keep", en_v21_config));
+ EXPECT_NE(nullptr, test::GetValueForConfig<String>(
+ table.get(), "android:string/keep", land_config));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index eea4306..d808da3 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -14,360 +14,383 @@
* limitations under the License.
*/
+#include "link/TableMerger.h"
+
+#include "android-base/logging.h"
+
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "link/TableMerger.h"
#include "util/Util.h"
-#include <cassert>
-
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");
+TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table,
+ const TableMergerOptions& options)
+ : context_(context), master_table_(out_table), options_(options) {
+ // Create the desired package that all tables will be merged into.
+ master_package_ = master_table_->CreatePackage(
+ context_->GetCompilationPackage(), context_->GetPackageId());
+ CHECK(master_package_ != nullptr) << "package name or ID already taken";
}
-bool TableMerger::merge(const Source& src, ResourceTable* table,
+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,
+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 */,
+ options_.auto_add_overlay);
}
/**
* 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();
+bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
+ io::IFileCollection* collection, bool overlay,
+ bool allow_new) {
+ const uint8_t desired_package_id = context_->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() != desired_package_id) {
+ context_->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() ||
+ context_->GetCompilationPackage() == package->name) {
+ FileMergeCallback callback;
+ if (collection) {
+ callback = [&](const ResourceNameRef& name,
+ const ConfigDescription& config, FileReference* new_file,
+ FileReference* old_file) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->FindFile(*old_file->path);
+ if (!f) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "file '" << *old_file->path
+ << "' not found");
+ return false;
+ }
+
+ new_file->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,
+ allow_new, 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& package_name,
+ ResourceTable* table,
+ io::IFileCollection* collection) {
+ bool error = false;
+ for (auto& package : table->packages) {
+ // Warn of packages with an unrelated ID.
+ if (package_name != package->name) {
+ context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package "
+ << package->name);
+ continue;
}
- return !error;
+
+ bool mangle = package_name != context_->GetCompilationPackage();
+ merged_packages_.insert(package->name);
+
+ auto callback = [&](
+ const ResourceNameRef& name, const ConfigDescription& config,
+ FileReference* new_file, FileReference* old_file) -> bool {
+ // The old file's path points inside the APK, so we can use it as is.
+ io::IFile* f = collection->FindFile(*old_file->path);
+ if (!f) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "file '" << *old_file->path << "' not found");
+ return false;
+ }
+
+ new_file->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* dst_type,
+ ResourceTableType* src_type) {
+ if (dst_type->symbol_status.state < src_type->symbol_status.state) {
+ // The incoming type's visibility is stronger, so we should override
+ // the visibility.
+ if (src_type->symbol_status.state == SymbolState::kPublic) {
+ // Only copy the ID if the source is public, or else the ID is
+ // meaningless.
+ dst_type->id = src_type->id;
}
- return true;
+ dst_type->symbol_status = std::move(src_type->symbol_status);
+ } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
+ src_type->symbol_status.state == SymbolState::kPublic &&
+ dst_type->id && src_type->id &&
+ dst_type->id.value() != src_type->id.value()) {
+ // Both types are public and have different IDs.
+ context->GetDiagnostics()->Error(DiagMessage(src)
+ << "cannot merge type '" << src_type->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* dst_entry, ResourceEntry* src_entry) {
+ if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
+ // The incoming type's visibility is stronger, so we should override
+ // the visibility.
+ if (src_entry->symbol_status.state == SymbolState::kPublic) {
+ // Only copy the ID if the source is public, or else the ID is
+ // meaningless.
+ dst_entry->id = src_entry->id;
}
- return true;
+ dst_entry->symbol_status = std::move(src_entry->symbol_status);
+ } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
+ dst_entry->symbol_status.state == SymbolState::kPublic &&
+ dst_entry->id && src_entry->id &&
+ dst_entry->id.value() != src_entry->id.value()) {
+ // Both entries are public and have different IDs.
+ context->GetDiagnostics()->Error(
+ DiagMessage(src) << "cannot merge entry '" << src_entry->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* existing_styleable = ValueCast<Styleable>(existing)) {
+ if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
+ // Styleables get merged.
+ existing_styleable->MergeWith(incoming_styleable);
+ 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& res_name, const bool overlay,
+ ResourceConfigValue* dst_config_value,
+ ResourceConfigValue* src_config_value) {
+ using CollisionResult = ResourceTable::CollisionResult;
- Value* dstValue = dstConfigValue->value.get();
- Value* srcValue = srcConfigValue->value.get();
+ Value* dst_value = dst_config_value->value.get();
+ Value* src_value = src_config_value->value.get();
- CollisionResult collisionResult;
+ CollisionResult collision_result;
+ if (overlay) {
+ collision_result = ResolveMergeCollision(dst_value, src_value);
+ } else {
+ collision_result =
+ ResourceTable::ResolveValueCollision(dst_value, src_value);
+ }
+
+ if (collision_result == 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(src_value->GetSource())
+ << "resource '" << res_name << "' has a conflicting value for "
+ << "configuration (" << src_config_value->config << ")");
+ context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource())
+ << "originally defined here");
+ return CollisionResult::kConflict;
+ }
+ return collision_result;
}
-bool TableMerger::doMerge(const Source& src,
- ResourceTable* srcTable,
- ResourceTablePackage* srcPackage,
- const bool manglePackage,
- const bool overlay,
- const bool allowNewResources,
+bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
+ ResourceTablePackage* src_package,
+ const bool mangle_package, const bool overlay,
+ const bool allow_new_resources,
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& src_type : src_package->types) {
+ ResourceTableType* dst_type =
+ master_package_->FindOrCreateType(src_type->type);
+ if (!MergeType(context_, src, dst_type, src_type.get())) {
+ error = true;
+ continue;
+ }
+
+ for (auto& src_entry : src_type->entries) {
+ std::string entry_name = src_entry->name;
+ if (mangle_package) {
+ entry_name =
+ NameMangler::MangleEntry(src_package->name, src_entry->name);
+ }
+
+ ResourceEntry* dst_entry;
+ if (allow_new_resources) {
+ dst_entry = dst_type->FindOrCreateEntry(entry_name);
+ } else {
+ dst_entry = dst_type->FindEntry(entry_name);
+ }
+
+ const ResourceNameRef res_name(src_package->name, src_type->type,
+ src_entry->name);
+
+ if (!dst_entry) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(src) << "resource " << res_name
+ << " does not override an existing resource");
+ context_->GetDiagnostics()->Note(
+ DiagMessage(src) << "define an <add-resource> tag or use "
+ << "--auto-add-overlay");
+ error = true;
+ continue;
+ }
+
+ if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
+ error = true;
+ continue;
+ }
+
+ for (auto& src_config_value : src_entry->values) {
+ using CollisionResult = ResourceTable::CollisionResult;
+
+ ResourceConfigValue* dst_config_value = dst_entry->FindValue(
+ src_config_value->config, src_config_value->product);
+ if (dst_config_value) {
+ CollisionResult collision_result =
+ MergeConfigValue(context_, res_name, overlay, dst_config_value,
+ src_config_value.get());
+ if (collision_result == CollisionResult::kConflict) {
error = true;
continue;
+ } else if (collision_result == CollisionResult::kKeepOriginal) {
+ continue;
+ }
+ } else {
+ dst_config_value = dst_entry->FindOrCreateValue(
+ src_config_value->config, src_config_value->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>(src_config_value->value.get())) {
+ std::unique_ptr<FileReference> new_file_ref;
+ if (mangle_package) {
+ new_file_ref = CloneAndMangleFile(src_package->name, *f);
+ } else {
+ new_file_ref = std::unique_ptr<FileReference>(
+ f->Clone(&master_table_->string_pool));
+ }
+
+ if (callback) {
+ if (!callback(res_name, src_config_value->config,
+ new_file_ref.get(), f)) {
+ error = true;
+ continue;
}
+ }
+ dst_config_value->value = std::move(new_file_ref);
- 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 {
+ dst_config_value->value = std::unique_ptr<Value>(
+ src_config_value->value->Clone(&master_table_->string_pool));
}
+ }
}
- 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& file_ref) {
+ StringPiece prefix, entry, suffix;
+ if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
+ std::string mangled_entry =
+ NameMangler::MangleEntry(package, entry.ToString());
+ std::string newPath = prefix.ToString() + mangled_entry + suffix.ToString();
+ std::unique_ptr<FileReference> new_file_ref =
+ util::make_unique<FileReference>(
+ master_table_->string_pool.MakeRef(newPath));
+ new_file_ref->SetComment(file_ref.GetComment());
+ new_file_ref->SetSource(file_ref.GetSource());
+ return new_file_ref;
+ }
+ return std::unique_ptr<FileReference>(
+ file_ref.Clone(&master_table_->string_pool));
}
-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& file_desc, io::IFile* file,
+ bool overlay) {
+ ResourceTable table;
+ std::string path = ResourceUtils::BuildResourceFileName(file_desc);
+ std::unique_ptr<FileReference> file_ref =
+ util::make_unique<FileReference>(table.string_pool.MakeRef(path));
+ file_ref->SetSource(file_desc.source);
+ file_ref->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(file_desc.name.package, 0x0);
+ pkg->FindOrCreateType(file_desc.name.type)
+ ->FindOrCreateEntry(file_desc.name.entry)
+ ->FindOrCreateValue(file_desc.config, {})
+ ->value = std::move(file_ref);
- 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 */);
+bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) {
+ return MergeFileImpl(file_desc, file, false /* overlay */);
}
-bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) {
- return mergeFileImpl(fileDesc, file, true /* overlay */);
+bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc,
+ io::IFile* file) {
+ return MergeFileImpl(file_desc, file, true /* overlay */);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 3473a27..4ab83c3f2 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -17,6 +17,11 @@
#ifndef AAPT_TABLEMERGER_H
#define AAPT_TABLEMERGER_H
+#include <functional>
+#include <map>
+
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -25,102 +30,114 @@
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
-#include <functional>
-#include <map>
-
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 auto_add_overlay = 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 out_table ResourceTable must live longer than this TableMerger.
+ * References are made to this ResourceTable for efficiency reasons.
+ */
+ TableMerger(IAaptContext* context, ResourceTable* out_table,
+ const TableMergerOptions& options);
- const std::set<std::string>& getMergedPackages() const {
- return mMergedPackages;
- }
+ const std::set<std::string>& merged_packages() const {
+ return merged_packages_;
+ }
- /**
- * 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:
+ DISALLOW_COPY_AND_ASSIGN(TableMerger);
- IAaptContext* mContext;
- ResourceTable* mMasterTable;
- TableMergerOptions mOptions;
- ResourceTablePackage* mMasterPackage;
+ using FileMergeCallback = std::function<bool(const ResourceNameRef&,
+ const ConfigDescription& config,
+ FileReference*, FileReference*)>;
- std::set<std::string> mMergedPackages;
+ IAaptContext* context_;
+ ResourceTable* master_table_;
+ TableMergerOptions options_;
+ ResourceTablePackage* master_package_;
+ std::set<std::string> merged_packages_;
- bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay);
+ bool MergeFileImpl(const ResourceFile& file_desc, 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* src_table,
+ io::IFileCollection* collection, bool overlay, bool allow_new);
- 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* src_table,
+ ResourceTablePackage* src_package, const bool mangle_package,
+ const bool overlay, const bool allow_new_resources,
+ 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..742f5a7 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -14,303 +14,334 @@
* 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"
-
-#include <gtest/gtest.h>
+#include "test/Test.h"
namespace aapt {
struct TableMergerTest : public ::testing::Test {
- std::unique_ptr<IAaptContext> mContext;
+ std::unique_ptr<IAaptContext> context_;
- void SetUp() override {
- mContext = test::ContextBuilder()
- // We are compiling this package.
- .setCompilationPackage("com.app.a")
+ void SetUp() override {
+ context_ =
+ 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> table_a =
+ 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> table_b = 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 final_table;
+ TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
+ io::FileCollection collection;
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection));
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(
+ merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
- EXPECT_TRUE(merger.getMergedPackages().count("com.app.b") != 0);
+ EXPECT_TRUE(merger.merged_packages().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(
+ final_table.FindResource(test::ParseNameOrDie("com.app.a:id/foo")));
+ AAPT_EXPECT_TRUE(
+ final_table.FindResource(test::ParseNameOrDie("com.app.a:id/bar")));
+ AAPT_EXPECT_TRUE(final_table.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(
+ final_table.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(final_table.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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, 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 file_desc;
+ file_desc.config = test::ParseConfigOrDie("hdpi-v4");
+ file_desc.name = test::ParseNameOrDie("layout/main");
+ file_desc.source = Source("res/layout-hdpi/main.xml");
+ test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat");
- ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile));
+ ASSERT_TRUE(merger.MergeFile(file_desc, &test_file));
- 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>(
+ &final_table, "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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
- 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 file_desc;
+ file_desc.name = test::ParseNameOrDie("xml/foo");
+ test::TestFile file_a("path/to/fileA.xml.flat");
+ test::TestFile file_b("path/to/fileB.xml.flat");
- ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
- ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
+ ASSERT_TRUE(merger.MergeFile(file_desc, &file_a));
+ ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b));
}
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> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ 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 final_table;
+ TableMerger merger(context_.get(), &final_table, 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({}, table_a.get()));
+ ASSERT_TRUE(
+ merger.MergeAndMangle({}, "com.app.b", table_b.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>(&final_table, "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>(&final_table,
+ "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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
- 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>(&final_table, "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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
- 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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
- 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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
- 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> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", {}, SymbolState::kUndefined)
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
+ .Build();
- ResourceTable finalTable;
- TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+ ResourceTable final_table;
+ TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, table_b.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> table_a =
+ test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_b =
+ 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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, table_b.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> table_a =
+ test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_b =
+ 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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_FALSE(merger.MergeOverlay({}, table_b.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> table_a =
+ 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> table_b =
+ 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 final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.merge({}, tableA.get()));
- ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
- Debug::printTable(&finalTable, {});
+ Styleable* styleable =
+ test::GetValue<Styleable>(&final_table, "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> expected_refs = {
+ 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(expected_refs, styleable->entries);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/VersionCollapser.cpp b/tools/aapt2/link/VersionCollapser.cpp
index 949d656..3df5899 100644
--- a/tools/aapt2/link/VersionCollapser.cpp
+++ b/tools/aapt2/link/VersionCollapser.cpp
@@ -14,139 +14,149 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "link/Linkers.h"
#include <algorithm>
#include <vector>
+#include "ResourceTable.h"
+
namespace aapt {
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())
+ : current_(begin), end_(end), pred_(pred) {
+ Advance();
+ }
- bool hasNext() {
- return mCurrent != mEnd;
- }
+ bool HasNext() { return current_ != end_; }
- Iterator nextIter() {
- Iterator iter = mCurrent;
- ++mCurrent;
- advance();
- return iter;
- }
+ Iterator NextIter() {
+ Iterator iter = current_;
+ ++current_;
+ 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 (; current_ != end_; ++current_) {
+ if (pred_(*current_)) {
+ return;
+ }
}
+ }
- Iterator mCurrent, mEnd;
- Pred mPred;
+ Iterator current_, end_;
+ Pred pred_;
};
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> make_filter_iterator(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() = {};
- }
- }
+static void CollapseVersions(int min_sdk, 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;
}
- // 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 <= min_sdk) {
+ // 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 config_without_sdk = config.CopyWithoutSdkVersion();
+ 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.
+ config_without_sdk.sdkVersion = val->config.sdkVersion;
+ return config_without_sdk == val->config &&
+ val->config.sdkVersion <= min_sdk;
+ };
+
+ // Remove the rest that match.
+ auto filter_iter =
+ make_filter_iterator(iter + 1, entry->values.rend(), pred);
+ while (filter_iter.HasNext()) {
+ filter_iter.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>& config_value : entry->values) {
+ if (config_value->config.sdkVersion != 0 &&
+ config_value->config.sdkVersion <= min_sdk) {
+ // Override the resource with a Configuration without an SDK.
+ std::unique_ptr<ResourceConfigValue> new_value =
+ util::make_unique<ResourceConfigValue>(
+ config_value->config.CopyWithoutSdkVersion(),
+ config_value->product);
+ new_value->value = std::move(config_value->value);
+ config_value = std::move(new_value);
+
+ 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());
- }
- }
+bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) {
+ const int min_sdk = context->GetMinSdkVersion();
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ CollapseVersions(min_sdk, 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..1b5592f 100644
--- a/tools/aapt2/link/VersionCollapser_test.cpp
+++ b/tools/aapt2/link/VersionCollapser_test.cpp
@@ -15,89 +15,103 @@
*/
#include "link/Linkers.h"
+
#include "test/Test.h"
namespace aapt {
-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 std::unique_ptr<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();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetMinSdkVersion(7).Build();
- const StringPiece resName = "@android:string/foo";
+ const StringPiece res_name = "@android:string/foo";
- uptr<ResourceTable> table =
- buildTableWithConfigs(resName,
- { "land-v4", "land-v5", "sw600dp", "land-v6",
- "land-v14", "land-v21" });
+ std::unique_ptr<ResourceTable> table = BuildTableWithConfigs(
+ res_name,
+ {"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(), res_name,
+ test::ParseConfigOrDie("land-v4")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ 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(), res_name,
+ 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(), res_name,
+ 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(), res_name,
+ test::ParseConfigOrDie("land")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v14")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v21")));
}
TEST(VersionCollapserTest, CollapseVersionsWhenMinSdkIsHighest) {
- uptr<IAaptContext> context = test::ContextBuilder().setMinSdkVersion(21).build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetMinSdkVersion(21).Build();
- const StringPiece resName = "@android:string/foo";
+ const StringPiece res_name = "@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()));
+ std::unique_ptr<ResourceTable> table = BuildTableWithConfigs(
+ res_name, {"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(), res_name,
+ test::ParseConfigOrDie("land-v4")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v5")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v6")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ 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(), res_name,
+ 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(), res_name,
+ test::ParseConfigOrDie("land")));
+ // land-v22 should remain as-is.
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(table.get(), res_name,
+ test::ParseConfigOrDie("land-v22")));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp
index 9f95177..24aa566 100644
--- a/tools/aapt2/link/XmlNamespaceRemover.cpp
+++ b/tools/aapt2/link/XmlNamespaceRemover.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "link/Linkers.h"
#include <algorithm>
+#include "ResourceTable.h"
+
namespace aapt {
namespace {
@@ -27,57 +28,61 @@
* Visits each xml Node, removing URI references and nested namespaces.
*/
class XmlVisitor : public xml::Visitor {
-public:
- XmlVisitor(bool keepUris) : mKeepUris(keepUris) {
- }
+ public:
+ explicit XmlVisitor(bool keep_uris) : keep_uris_(keep_uris) {}
- 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 (!keep_uris_) {
+ for (xml::Attribute& attr : el->attributes) {
+ attr.namespace_uri = std::string();
+ }
+ el->namespace_uri = std::string();
+ }
+ xml::Visitor::Visit(el);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlVisitor);
+
+ bool keep_uris_;
};
-} // 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(keep_uris_);
+ 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..a176c03 100644
--- a/tools/aapt2/link/XmlNamespaceRemover_test.cpp
+++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
@@ -15,95 +15,108 @@
*/
#include "link/Linkers.h"
+
#include "test/Test.h"
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:
+ XmlUriTestVisitor() = default;
- void visit(xml::Namespace* ns) override {
- EXPECT_EQ(std::string(), ns->namespaceUri);
- xml::Visitor::visit(ns);
+ void Visit(xml::Element* el) override {
+ for (const auto& attr : el->attributes) {
+ EXPECT_EQ(std::string(), attr.namespace_uri);
}
+ EXPECT_EQ(std::string(), el->namespace_uri);
+ xml::Visitor::Visit(el);
+ }
+
+ void Visit(xml::Namespace* ns) override {
+ EXPECT_EQ(std::string(), ns->namespace_uri);
+ xml::Visitor::Visit(ns);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlUriTestVisitor);
};
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:
+ XmlNamespaceTestVisitor() = default;
+
+ void Visit(xml::Namespace* ns) override {
+ ADD_FAILURE() << "Detected namespace: " << ns->namespace_prefix << "=\""
+ << ns->namespace_uri << "\"";
+ xml::Visitor::Visit(ns);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlNamespaceTestVisitor);
};
class XmlNamespaceRemoverTest : public ::testing::Test {
-public:
- void SetUp() override {
- mContext = test::ContextBuilder()
- .setCompilationPackage("com.app.test")
- .build();
- }
+ public:
+ void SetUp() override {
+ context_ =
+ test::ContextBuilder().SetCompilationPackage("com.app.test").Build();
+ }
-protected:
- std::unique_ptr<IAaptContext> mContext;
+ protected:
+ std::unique_ptr<IAaptContext> context_;
};
TEST_F(XmlNamespaceRemoverTest, RemoveUris) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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(context_.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(context_.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(context_.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(context_.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(context_.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..a819831 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -14,10 +14,11 @@
* limitations under the License.
*/
+#include "link/Linkers.h"
+
#include "Diagnostics.h"
#include "ResourceUtils.h"
#include "SdkConstants.h"
-#include "link/Linkers.h"
#include "link/ReferenceLinker.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -29,146 +30,161 @@
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)
+ : context_(context),
+ symbols_(symbols),
+ decls_(decls),
+ callsite_(callsite),
+ error_(false) {}
+
+ void Visit(Reference* ref) override {
+ if (!ReferenceLinker::LinkReference(ref, context_, symbols_, decls_,
+ callsite_)) {
+ error_ = true;
}
+ }
- void visit(Reference* ref) override {
- if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
- mError = true;
- }
- }
+ bool HasError() const { return error_; }
- bool hasError() const {
- return mError;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor);
-private:
- IAaptContext* mContext;
- SymbolTable* mSymbols;
- xml::IPackageDeclStack* mDecls;
- CallSite* mCallSite;
- bool mError;
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ xml::IPackageDeclStack* decls_;
+ CallSite* callsite_;
+ bool error_;
};
/**
* 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>* sdk_levels_found, CallSite* callsite)
+ : context_(context),
+ symbols_(symbols),
+ source_(source),
+ sdk_levels_found_(sdk_levels_found),
+ callsite_(callsite),
+ reference_visitor_(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 = source_.WithLine(el->line_number);
+ for (xml::Attribute& attr : el->attributes) {
+ Maybe<xml::ExtractedPackage> maybe_package =
+ xml::ExtractPackageFromNamespace(attr.namespace_uri);
+ if (maybe_package) {
+ // There is a valid package name for this attribute. We will look this
+ // up.
+ StringPiece package = maybe_package.value().package;
+ if (package.empty()) {
+ // Empty package means the 'current' or 'local' package.
+ package = context_->GetCompilationPackage();
}
- // Call the super implementation.
- xml::PackageAwareVisitor::visit(el);
+ Reference attr_ref(
+ ResourceNameRef(package, ResourceType::kAttr, attr.name));
+ attr_ref.private_reference = maybe_package.value().private_namespace;
+
+ std::string err_str;
+ attr.compiled_attribute = ReferenceLinker::CompileXmlAttribute(
+ attr_ref, context_->GetNameMangler(), symbols_, callsite_,
+ &err_str);
+
+ // Convert the string value into a compiled Value if this is a valid
+ // attribute.
+ if (attr.compiled_attribute) {
+ if (attr.compiled_attribute.value().id) {
+ // Record all SDK levels from which the attributes were defined.
+ const size_t sdk_level = FindAttributeSdkLevel(
+ attr.compiled_attribute.value().id.value());
+ if (sdk_level > 1) {
+ sdk_levels_found_->insert(sdk_level);
+ }
+ }
+
+ const Attribute* attribute =
+ &attr.compiled_attribute.value().attribute;
+ attr.compiled_value =
+ ResourceUtils::TryParseItemForAttribute(attr.value, attribute);
+ if (!attr.compiled_value &&
+ !(attribute->type_mask & android::ResTable_map::TYPE_STRING)) {
+ // We won't be able to encode this as a string.
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source) << "'" << attr.value << "' "
+ << "is incompatible with attribute "
+ << package << ":" << attr.name << " "
+ << *attribute);
+ error_ = true;
+ }
+
+ } else {
+ context_->GetDiagnostics()->Error(DiagMessage(source)
+ << "attribute '" << package << ":"
+ << attr.name << "' " << err_str);
+ error_ = true;
+ }
+ } else if (!attr.compiled_value) {
+ // We still encode references, but only if we haven't manually set this
+ // to
+ // another compiled value.
+ attr.compiled_value = ResourceUtils::TryParseReference(attr.value);
+ }
+
+ if (attr.compiled_value) {
+ // With a compiledValue, we must resolve the reference and assign it an
+ // ID.
+ attr.compiled_value->SetSource(source);
+ attr.compiled_value->Accept(&reference_visitor_);
+ }
}
- 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 error_ || reference_visitor_.HasError(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(XmlVisitor);
+
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ Source source_;
+ std::set<int>* sdk_levels_found_;
+ CallSite* callsite_;
+ ReferenceVisitor reference_visitor_;
+ bool error_ = 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) {
+ sdk_levels_found_.clear();
+ CallSite callsite = {resource->file.name};
+ XmlVisitor visitor(context, context->GetExternalSymbols(),
+ resource->file.source, &sdk_levels_found_, &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..810f63c 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -15,242 +15,285 @@
*/
#include "link/Linkers.h"
+
#include "test/Test.h"
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 {
+ context_ =
+ 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> context_;
};
TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ std::unique_ptr<xml::XmlResource> doc =
+ test::BuildXmlDomForPackageName(context_.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(context_.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, 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* xml_attr =
+ view_el->FindAttribute(xml::kSchemaAndroid, "layout_width");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x01010000));
+ ASSERT_NE(xml_attr->compiled_value, nullptr);
+ ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.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));
+ xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "background");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x01010001));
+ ASSERT_NE(xml_attr->compiled_value, nullptr);
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.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.
+ xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ ASSERT_FALSE(
+ xml_attr->compiled_value); // 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);
+ xml_attr = view_el->FindAttribute("", "class");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_FALSE(xml_attr->compiled_attribute);
+ ASSERT_EQ(xml_attr->compiled_value, 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(context_.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(context_.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(context_.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(context_.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(context_.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(context_.get(), doc.get()));
+ EXPECT_TRUE(linker.sdk_levels().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(context_.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(context_.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, 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* xml_attr = view_el->FindAttribute(
+ xml::BuildPackageNamespace("com.android.support"), "colorAccent");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010001));
+ ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.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(context_.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(context_.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, 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* xml_attr =
+ view_el->FindAttribute(xml::kSchemaAuto, "colorAccent");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010000));
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.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(context_.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(context_.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, 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* xml_attr =
+ view_el->FindAttribute(xml::kSchemaAndroid, "attr");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x01010002));
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.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(view_el->GetChildElements().empty());
+ view_el = view_el->GetChildElements().front();
+ ASSERT_NE(view_el, 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).
+ xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"),
+ "attr");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010002));
+ ref = ValueCast<Reference>(xml_attr->compiled_value.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(context_.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(context_.get(), doc.get()));
- xml::Element* viewEl = xml::findRootElement(doc.get());
- ASSERT_NE(viewEl, nullptr);
+ xml::Element* view_el = xml::FindRootElement(doc.get());
+ ASSERT_NE(view_el, 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* xml_attr = view_el->FindAttribute(
+ xml::BuildPackageNamespace("com.app.test"), "attr");
+ ASSERT_NE(xml_attr, nullptr);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute);
+ AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id);
+ EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(),
+ ResourceId(0x7f010002));
+ Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.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..4526a79 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -17,37 +17,37 @@
#ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H
#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H
+#include <iostream>
+#include <list>
+#include <sstream>
+
#include "Diagnostics.h"
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceValues.h"
#include "Source.h"
-#include <iostream>
-#include <list>
-#include <sstream>
-
namespace aapt {
class ResourceTable;
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 IsVerbose() = 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..767384d 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -14,267 +14,288 @@
* limitations under the License.
*/
+#include "process/SymbolTable.h"
+
+#include "androidfw/AssetManager.h"
+#include "androidfw/ResourceTypes.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>
-#include <androidfw/ResourceTypes.h>
-
namespace aapt {
-void SymbolTable::appendSource(std::unique_ptr<ISymbolSource> source) {
- mSources.push_back(std::move(source));
+void SymbolTable::AppendSource(std::unique_ptr<ISymbolSource> source) {
+ sources_.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));
+void SymbolTable::PrependSource(std::unique_ptr<ISymbolSource> source) {
+ sources_.insert(sources_.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.
+ cache_.clear();
}
-const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) {
- if (const std::shared_ptr<Symbol>& s = mCache.get(name)) {
- return s.get();
- }
+const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) {
+ if (const std::shared_ptr<Symbol>& s = cache_.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 : sources_) {
+ 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> shared_symbol =
+ std::shared_ptr<Symbol>(symbol.release());
+ cache_.put(name, shared_symbol);
- if (sharedSymbol->id) {
- // The symbol has an ID, so we can also cache this!
- mIdCache.put(sharedSymbol->id.value(), sharedSymbol);
- }
- return sharedSymbol.get();
- }
+ if (shared_symbol->id) {
+ // The symbol has an ID, so we can also cache this!
+ id_cache_.put(shared_symbol->id.value(), shared_symbol);
+ }
+ return shared_symbol.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();
- }
+const SymbolTable::Symbol* SymbolTable::FindById(const ResourceId& id) {
+ if (const std::shared_ptr<Symbol>& s = id_cache_.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 : sources_) {
+ 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> shared_symbol =
+ std::shared_ptr<Symbol>(symbol.release());
+ id_cache_.put(id, shared_symbol);
+ return shared_symbol.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());
- }
+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());
+ }
- 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));
- }
+std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName(
+ const ResourceName& name) {
+ Maybe<ResourceTable::SearchResult> result = table_->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->is_public = (sr.entry->symbol_status.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* config_value = sr.entry->FindValue(kDefaultConfig);
+ if (config_value) {
+ // This resource has an Attribute.
+ if (Attribute* attr = ValueCast<Attribute>(config_value->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);
+bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
+ int32_t cookie = 0;
+ return assets_.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->type_mask = 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& map_entry = entry[i].map;
+ if (Res_INTERNALID(map_entry.name.ident)) {
+ switch (map_entry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->min_int = static_cast<int32_t>(map_entry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->max_int = static_cast<int32_t>(map_entry.value.data);
break;
}
+ continue;
+ }
+
+ android::ResTable::resource_name entry_name;
+ if (!table.getResourceName(map_entry.name.ident, false, &entry_name)) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ Maybe<ResourceName> parsed_name =
+ ResourceUtils::ToResourceName(entry_name);
+ if (!parsed_name) {
+ return nullptr;
+ }
+
+ Attribute::Symbol symbol;
+ symbol.symbol.name = parsed_name.value();
+ symbol.symbol.id = ResourceId(map_entry.name.ident);
+ symbol.value = map_entry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
}
+ }
+ table.unlockBag(entry);
+ return s;
+}
- 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;
- }
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
+ const ResourceName& name) {
+ const android::ResTable& table = assets_.getResources(false);
- android::ResTable::resource_name entryName;
- if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
- table.unlockBag(entry);
- return nullptr;
- }
+ 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);
- Maybe<ResourceName> parsedName = ResourceUtils::toResourceName(entryName);
- if (!parsedName) {
- return nullptr;
- }
+ uint32_t type_spec_flags = 0;
+ ResourceId res_id = table.identifierForName(
+ entry16.data(), entry16.size(), type16.data(), type16.size(),
+ package16.data(), package16.size(), &type_spec_flags);
+ if (!res_id.is_valid()) {
+ return {};
+ }
- 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);
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (name.type == ResourceType::kAttr) {
+ s = LookupAttributeInTable(table, res_id);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = res_id;
+ }
+
+ if (s) {
+ s->is_public =
+ (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
return s;
+ }
+ return {};
}
-std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByName(
- 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);
-
- 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;
- }
+static Maybe<ResourceName> GetResourceName(const android::ResTable& table,
+ ResourceId id) {
+ android::ResTable::resource_name res_name = {};
+ if (!table.getResourceName(id.id, true, &res_name)) {
return {};
+ }
+ return ResourceUtils::ToResourceName(res_name);
}
-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 = assets_.getResources(false);
+ Maybe<ResourceName> maybe_name = GetResourceName(table, id);
+ if (!maybe_name) {
return {};
+ }
+
+ uint32_t type_spec_flags = 0;
+ table.getResourceFlags(id.id, &type_spec_flags);
+
+ std::unique_ptr<SymbolTable::Symbol> s;
+ if (maybe_name.value().type == ResourceType::kAttr) {
+ s = LookupAttributeInTable(table, id);
+ } else {
+ s = util::make_unique<SymbolTable::Symbol>();
+ s->id = id;
+ }
+
+ if (s) {
+ s->is_public =
+ (type_spec_flags & 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 {};
+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 {};
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index bd31416..25f7565 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -17,116 +17,115 @@
#ifndef AAPT_PROCESS_SYMBOLTABLE_H
#define AAPT_PROCESS_SYMBOLTABLE_H
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "androidfw/AssetManager.h"
+#include "utils/JenkinsHash.h"
+#include "utils/LruCache.h"
+
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "util/Util.h"
-#include <utils/JenkinsHash.h>
-#include <utils/LruCache.h>
-
-#include <android-base/macros.h>
-#include <androidfw/AssetManager.h>
-#include <algorithm>
-#include <memory>
-#include <vector>
-
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> str_hash;
+ android::hash_t hash = 0;
+ hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.package));
+ hash = android::JenkinsHashMix(hash, (uint32_t)name.type);
+ hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(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), is_public(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 is_public = false;
+ };
- SymbolTable() : mCache(200), mIdCache(200) {
- }
+ SymbolTable() : cache_(200), id_cache_(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 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>> sources_;
- // 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>> cache_;
+ android::LruCache<ResourceId, std::shared_ptr<Symbol>> id_cache_;
- 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 +134,40 @@
* Lookups by ID are ignored.
*/
class ResourceTableSymbolSource : public ISymbolSource {
-public:
- explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) {
- }
+ public:
+ explicit ResourceTableSymbolSource(ResourceTable* table) : table_(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* table_;
- 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 assets_;
- 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..9ea0786 100644
--- a/tools/aapt2/process/SymbolTable_test.cpp
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -15,39 +15,44 @@
*/
#include "process/SymbolTable.h"
+
#include "test/Test.h"
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 symbol_source(table.get());
+ EXPECT_NE(nullptr,
+ symbol_source.FindByName(test::ParseNameOrDie("android:id/foo")));
+ EXPECT_NE(nullptr,
+ symbol_source.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 =
+ symbol_source.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 symbol_source(table.get());
+ std::unique_ptr<SymbolTable::Symbol> s =
+ symbol_source.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.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
index 2aa8aa5..38bf4e3 100644
--- a/tools/aapt2/proto/ProtoHelpers.cpp
+++ b/tools/aapt2/proto/ProtoHelpers.cpp
@@ -18,120 +18,151 @@
namespace aapt {
-void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool) {
- BigBuffer buffer(1024);
- StringPool::flattenUtf8(&buffer, pool);
+void SerializeStringPoolToPb(const StringPool& pool,
+ pb::StringPool* out_pb_pool) {
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf8(&buffer, pool);
- std::string* data = outPbPool->mutable_data();
- data->reserve(buffer.size());
+ std::string* data = out_pb_pool->mutable_data();
+ data->reserve(buffer.size());
- size_t offset = 0;
- for (const BigBuffer::Block& block : buffer) {
- data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
- offset += block.size;
- }
+ size_t offset = 0;
+ for (const BigBuffer::Block& block : buffer) {
+ data->insert(data->begin() + offset, block.buffer.get(),
+ block.buffer.get() + block.size);
+ offset += block.size;
+ }
}
-void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource) {
- StringPool::Ref ref = srcPool->makeRef(source.path);
- outPbSource->set_path_idx(static_cast<uint32_t>(ref.getIndex()));
- if (source.line) {
- outPbSource->set_line_no(static_cast<uint32_t>(source.line.value()));
- }
+void SerializeSourceToPb(const Source& source, StringPool* src_pool,
+ pb::Source* out_pb_source) {
+ StringPool::Ref ref = src_pool->MakeRef(source.path);
+ out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index()));
+ if (source.line) {
+ out_pb_source->set_line_no(static_cast<uint32_t>(source.line.value()));
+ }
}
-void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
- Source* outSource) {
- if (pbSource.has_path_idx()) {
- outSource->path = util::getString(srcPool, pbSource.path_idx());
- }
+void DeserializeSourceFromPb(const pb::Source& pb_source,
+ const android::ResStringPool& src_pool,
+ Source* out_source) {
+ if (pb_source.has_path_idx()) {
+ out_source->path = util::GetString(src_pool, pb_source.path_idx());
+ }
- if (pbSource.has_line_no()) {
- outSource->line = static_cast<size_t>(pbSource.line_no());
- }
+ if (pb_source.has_line_no()) {
+ out_source->line = static_cast<size_t>(pb_source.line_no());
+ }
}
-pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state) {
- switch (state) {
- case SymbolState::kPrivate: return pb::SymbolStatus_Visibility_Private;
- case SymbolState::kPublic: return pb::SymbolStatus_Visibility_Public;
- default: break;
- }
- return pb::SymbolStatus_Visibility_Unknown;
+pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
+ switch (state) {
+ case SymbolState::kPrivate:
+ return pb::SymbolStatus_Visibility_Private;
+ case SymbolState::kPublic:
+ return pb::SymbolStatus_Visibility_Public;
+ default:
+ break;
+ }
+ return pb::SymbolStatus_Visibility_Unknown;
}
-SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility) {
- switch (pbVisibility) {
- case pb::SymbolStatus_Visibility_Private: return SymbolState::kPrivate;
- case pb::SymbolStatus_Visibility_Public: return SymbolState::kPublic;
- default: break;
- }
- return SymbolState::kUndefined;
+SymbolState DeserializeVisibilityFromPb(
+ pb::SymbolStatus_Visibility pb_visibility) {
+ switch (pb_visibility) {
+ case pb::SymbolStatus_Visibility_Private:
+ return SymbolState::kPrivate;
+ case pb::SymbolStatus_Visibility_Public:
+ return SymbolState::kPublic;
+ default:
+ break;
+ }
+ return SymbolState::kUndefined;
}
-void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig) {
- android::ResTable_config flatConfig = config;
- flatConfig.size = sizeof(flatConfig);
- flatConfig.swapHtoD();
- outPbConfig->set_data(&flatConfig, sizeof(flatConfig));
+void SerializeConfig(const ConfigDescription& config,
+ pb::ConfigDescription* out_pb_config) {
+ android::ResTable_config flat_config = config;
+ flat_config.size = sizeof(flat_config);
+ flat_config.swapHtoD();
+ out_pb_config->set_data(&flat_config, sizeof(flat_config));
}
-bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
- ConfigDescription* outConfig) {
- if (!pbConfig.has_data()) {
- return false;
- }
+bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,
+ ConfigDescription* out_config) {
+ if (!pb_config.has_data()) {
+ return false;
+ }
- const android::ResTable_config* config;
- if (pbConfig.data().size() > sizeof(*config)) {
- return false;
- }
+ const android::ResTable_config* config;
+ if (pb_config.data().size() > sizeof(*config)) {
+ return false;
+ }
- config = reinterpret_cast<const android::ResTable_config*>(pbConfig.data().data());
- outConfig->copyFromDtoH(*config);
- return true;
+ config = reinterpret_cast<const android::ResTable_config*>(
+ pb_config.data().data());
+ out_config->copyFromDtoH(*config);
+ return true;
}
-pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type) {
- switch (type) {
- case Reference::Type::kResource: return pb::Reference_Type_Ref;
- case Reference::Type::kAttribute: return pb::Reference_Type_Attr;
- default: break;
- }
- return pb::Reference_Type_Ref;
+pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) {
+ switch (type) {
+ case Reference::Type::kResource:
+ return pb::Reference_Type_Ref;
+ case Reference::Type::kAttribute:
+ return pb::Reference_Type_Attr;
+ default:
+ break;
+ }
+ return pb::Reference_Type_Ref;
}
-Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType) {
- switch (pbType) {
- case pb::Reference_Type_Ref: return Reference::Type::kResource;
- case pb::Reference_Type_Attr: return Reference::Type::kAttribute;
- default: break;
- }
- return Reference::Type::kResource;
+Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) {
+ switch (pb_type) {
+ case pb::Reference_Type_Ref:
+ return Reference::Type::kResource;
+ case pb::Reference_Type_Attr:
+ return Reference::Type::kAttribute;
+ default:
+ break;
+ }
+ return Reference::Type::kResource;
}
-pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx) {
- switch (pluralIdx) {
- case Plural::Zero: return pb::Plural_Arity_Zero;
- case Plural::One: return pb::Plural_Arity_One;
- case Plural::Two: return pb::Plural_Arity_Two;
- case Plural::Few: return pb::Plural_Arity_Few;
- case Plural::Many: return pb::Plural_Arity_Many;
- default: break;
- }
- return pb::Plural_Arity_Other;
+pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) {
+ switch (plural_idx) {
+ case Plural::Zero:
+ return pb::Plural_Arity_Zero;
+ case Plural::One:
+ return pb::Plural_Arity_One;
+ case Plural::Two:
+ return pb::Plural_Arity_Two;
+ case Plural::Few:
+ return pb::Plural_Arity_Few;
+ case Plural::Many:
+ return pb::Plural_Arity_Many;
+ default:
+ break;
+ }
+ return pb::Plural_Arity_Other;
}
-size_t deserializePluralEnumFromPb(pb::Plural_Arity arity) {
- switch (arity) {
- case pb::Plural_Arity_Zero: return Plural::Zero;
- case pb::Plural_Arity_One: return Plural::One;
- case pb::Plural_Arity_Two: return Plural::Two;
- case pb::Plural_Arity_Few: return Plural::Few;
- case pb::Plural_Arity_Many: return Plural::Many;
- default: break;
- }
- return Plural::Other;
+size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) {
+ switch (arity) {
+ case pb::Plural_Arity_Zero:
+ return Plural::Zero;
+ case pb::Plural_Arity_One:
+ return Plural::One;
+ case pb::Plural_Arity_Two:
+ return Plural::Two;
+ case pb::Plural_Arity_Few:
+ return Plural::Few;
+ case pb::Plural_Arity_Many:
+ return Plural::Many;
+ default:
+ break;
+ }
+ return Plural::Other;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
index 02e67f1..735cda0 100644
--- a/tools/aapt2/proto/ProtoHelpers.h
+++ b/tools/aapt2/proto/ProtoHelpers.h
@@ -17,36 +17,45 @@
#ifndef AAPT_PROTO_PROTOHELPERS_H
#define AAPT_PROTO_PROTOHELPERS_H
+#include "androidfw/ResourceTypes.h"
+
#include "ConfigDescription.h"
#include "ResourceTable.h"
#include "Source.h"
#include "StringPool.h"
-
#include "proto/frameworks/base/tools/aapt2/Format.pb.h"
-#include <androidfw/ResourceTypes.h>
-
namespace aapt {
-void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool);
+void SerializeStringPoolToPb(const StringPool& pool,
+ pb::StringPool* out_pb_pool);
-void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource);
-void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
- Source* outSource);
+void SerializeSourceToPb(const Source& source, StringPool* src_pool,
+ pb::Source* out_pb_source);
-pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state);
-SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility);
+void DeserializeSourceFromPb(const pb::Source& pb_source,
+ const android::ResStringPool& src_pool,
+ Source* out_source);
-void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig);
-bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
- ConfigDescription* outConfig);
+pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state);
-pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type);
-Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType);
+SymbolState DeserializeVisibilityFromPb(
+ pb::SymbolStatus_Visibility pb_visibility);
-pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx);
-size_t deserializePluralEnumFromPb(pb::Plural_Arity arity);
+void SerializeConfig(const ConfigDescription& config,
+ pb::ConfigDescription* out_pb_config);
-} // namespace aapt
+bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,
+ ConfigDescription* out_config);
+
+pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type);
+
+Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type);
+
+pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx);
+
+size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity);
+
+} // namespace aapt
#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
index cc7c251..39c5003 100644
--- a/tools/aapt2/proto/ProtoSerialize.h
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -17,61 +17,61 @@
#ifndef AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+#include "android-base/macros.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
#include "Diagnostics.h"
#include "ResourceTable.h"
#include "Source.h"
#include "proto/ProtoHelpers.h"
-#include <android-base/macros.h>
-#include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-
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 out_;
};
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 in_;
};
-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<pb::ResourceTable> SerializeTableToPb(ResourceTable* table);
+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..d93d495 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -14,458 +14,476 @@
* limitations under the License.
*/
+#include "proto/ProtoSerialize.h"
+
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ValueVisitor.h"
#include "proto/ProtoHelpers.h"
-#include "proto/ProtoSerialize.h"
-
-#include <androidfw/ResourceTypes.h>
namespace aapt {
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)
+ : mapping_(mapping) {
+ CHECK(mapping_ != nullptr);
+ }
+
+ void Visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().is_valid()) {
+ 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 cache_iter = mapping_->find(id);
+ if (cache_iter != mapping_->end()) {
+ reference->name = cache_iter->second.ToResourceName();
}
+ }
-private:
- const std::map<ResourceId, ResourceNameRef>* mMapping;
+ private:
+ const std::map<ResourceId, ResourceNameRef>* mapping_;
};
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)
+ : value_pool_(valuePool),
+ source_pool_(sourcePool),
+ symbol_pool_(symbolPool),
+ source_(source),
+ diag_(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) {
+ diag_->Error(DiagMessage(source_) << "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(), *source_pool_,
+ &entry->symbol_status.source);
+ }
+
+ if (pbStatus.has_comment()) {
+ entry->symbol_status.comment = pbStatus.comment();
+ }
+
+ SymbolState visibility =
+ DeserializeVisibilityFromPb(pbStatus.visibility());
+ entry->symbol_status.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->symbol_status.state != SymbolState::kPublic) {
+ // If the type has not been made public, do so now.
+ type->symbol_status.state = SymbolState::kPublic;
+ if (pbType.has_id()) {
+ type->id = static_cast<uint8_t>(pbType.id());
+ }
}
+ } else if (visibility == SymbolState::kPrivate) {
+ if (type->symbol_status.state == SymbolState::kUndefined) {
+ type->symbol_status.state = SymbolState::kPrivate;
+ }
+ }
}
- ReferenceIdToNameVisitor visitor(&idIndex);
- visitAllValuesInPackage(pkg, &visitor);
- return true;
+ ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
+ if (resId.is_valid()) {
+ 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)) {
+ diag_->Error(DiagMessage(source_) << "invalid configuration");
+ return {};
+ }
+
+ ResourceConfigValue* configValue =
+ entry->FindOrCreateValue(config, pbConfig.product());
+ if (configValue->value) {
+ // Duplicate config.
+ diag_->Error(DiagMessage(source_) << "duplicate configuration");
+ return {};
+ }
+
+ configValue->value = DeserializeValueFromPb(
+ pbConfigValue.value(), config, &table->string_pool);
+ 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& pb_item,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ if (pb_item.has_ref()) {
+ const pb::Reference& pb_ref = pb_item.ref();
+ std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+ if (!DeserializeReferenceFromPb(pb_ref, ref.get())) {
+ return {};
+ }
+ return std::move(ref);
+
+ } else if (pb_item.has_prim()) {
+ const pb::Primitive& pb_prim = pb_item.prim();
+ android::Res_value prim = {};
+ prim.dataType = static_cast<uint8_t>(pb_prim.type());
+ prim.data = pb_prim.data();
+ return util::make_unique<BinaryPrimitive>(prim);
+
+ } else if (pb_item.has_id()) {
+ return util::make_unique<Id>();
+
+ } else if (pb_item.has_str()) {
+ const uint32_t idx = pb_item.str().idx();
+ const std::string str = util::GetString(*value_pool_, idx);
+
+ const android::ResStringPool_span* spans = value_pool_->styleAt(idx);
+ if (spans && spans->name.index != android::ResStringPool_span::END) {
+ StyleString style_str = {str};
+ while (spans->name.index != android::ResStringPool_span::END) {
+ style_str.spans.push_back(
+ Span{util::GetString(*value_pool_, spans->name.index),
+ spans->firstChar, spans->lastChar});
+ spans++;
+ }
+ return util::make_unique<StyledString>(pool->MakeRef(
+ style_str,
+ StringPool::Context(StringPool::Context::kStylePriority, config)));
+ }
+ return util::make_unique<String>(
+ pool->MakeRef(str, StringPool::Context(config)));
+
+ } else if (pb_item.has_raw_str()) {
+ const uint32_t idx = pb_item.raw_str().idx();
+ const std::string str = util::GetString(*value_pool_, idx);
+ return util::make_unique<RawString>(
+ pool->MakeRef(str, StringPool::Context(config)));
+
+ } else if (pb_item.has_file()) {
+ const uint32_t idx = pb_item.file().path_idx();
+ const std::string str = util::GetString(*value_pool_, idx);
+ return util::make_unique<FileReference>(pool->MakeRef(
+ str,
+ StringPool::Context(StringPool::Context::kHighPriority, config)));
+
+ } else {
+ diag_->Error(DiagMessage(source_) << "unknown item");
+ }
+ return {};
+ }
+
+ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
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 is_weak = pb_value.has_weak() ? pb_value.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 (pb_value.has_item()) {
+ value = DeserializeItemFromPb(pb_value.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 (pb_value.has_compound_value()) {
+ const pb::CompoundValue& pb_compound_value = pb_value.compound_value();
+ if (pb_compound_value.has_attr()) {
+ const pb::Attribute& pb_attr = pb_compound_value.attr();
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
+ attr->type_mask = pb_attr.format_flags();
+ attr->min_int = pb_attr.min_int();
+ attr->max_int = pb_attr.max_int();
+ for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbols()) {
+ Attribute::Symbol symbol;
+ DeserializeItemCommon(pb_symbol, &symbol.symbol);
+ if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) {
return {};
+ }
+ symbol.value = pb_symbol.value();
+ attr->symbols.push_back(std::move(symbol));
+ }
+ value = std::move(attr);
+
+ } else if (pb_compound_value.has_style()) {
+ const pb::Style& pb_style = pb_compound_value.style();
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (pb_style.has_parent()) {
+ style->parent = Reference();
+ if (!DeserializeReferenceFromPb(pb_style.parent(),
+ &style->parent.value())) {
+ return {};
+ }
+
+ if (pb_style.has_parent_source()) {
+ Source parent_source;
+ DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_,
+ &parent_source);
+ style->parent.value().SetSource(std::move(parent_source));
+ }
}
- assert(value && "forgot to set value");
+ for (const pb::Style_Entry& pb_entry : pb_style.entries()) {
+ Style::Entry entry;
+ DeserializeItemCommon(pb_entry, &entry.key);
+ if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) {
+ return {};
+ }
- value->setWeak(isWeak);
- deserializeItemCommon(pbValue, value.get());
- return value;
+ entry.value = DeserializeItemFromPb(pb_entry.item(), config, pool);
+ if (!entry.value) {
+ return {};
+ }
+
+ DeserializeItemCommon(pb_entry, entry.value.get());
+ style->entries.push_back(std::move(entry));
+ }
+ value = std::move(style);
+
+ } else if (pb_compound_value.has_styleable()) {
+ const pb::Styleable& pb_styleable = pb_compound_value.styleable();
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ for (const pb::Styleable_Entry& pb_entry : pb_styleable.entries()) {
+ Reference attr_ref;
+ DeserializeItemCommon(pb_entry, &attr_ref);
+ DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref);
+ styleable->entries.push_back(std::move(attr_ref));
+ }
+ value = std::move(styleable);
+
+ } else if (pb_compound_value.has_array()) {
+ const pb::Array& pb_array = pb_compound_value.array();
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const pb::Array_Entry& pb_entry : pb_array.entries()) {
+ std::unique_ptr<Item> item =
+ DeserializeItemFromPb(pb_entry.item(), config, pool);
+ if (!item) {
+ return {};
+ }
+
+ DeserializeItemCommon(pb_entry, item.get());
+ array->items.push_back(std::move(item));
+ }
+ value = std::move(array);
+
+ } else if (pb_compound_value.has_plural()) {
+ const pb::Plural& pb_plural = pb_compound_value.plural();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) {
+ size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity());
+ plural->values[pluralIdx] =
+ DeserializeItemFromPb(pb_entry.item(), config, pool);
+ if (!plural->values[pluralIdx]) {
+ return {};
+ }
+
+ DeserializeItemCommon(pb_entry, plural->values[pluralIdx].get());
+ }
+ value = std::move(plural);
+
+ } else {
+ diag_->Error(DiagMessage(source_) << "unknown compound value");
+ return {};
+ }
+ } else {
+ diag_->Error(DiagMessage(source_) << "unknown value");
+ return {};
}
- bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) {
- outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
- outRef->privateReference = pbRef.private_();
+ CHECK(value) << "forgot to set value";
- if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
- return false;
- }
+ value->SetWeak(is_weak);
+ DeserializeItemCommon(pb_value, value.get());
+ return value;
+ }
- if (pbRef.has_id()) {
- outRef->id = ResourceId(pbRef.id());
- }
+ bool DeserializeReferenceFromPb(const pb::Reference& pb_ref,
+ Reference* out_ref) {
+ out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
+ out_ref->private_reference = pb_ref.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 (!pb_ref.has_id() && !pb_ref.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 (pb_ref.has_id()) {
+ out_ref->id = ResourceId(pb_ref.id());
}
-private:
- const android::ResStringPool* mValuePool;
- const android::ResStringPool* mSourcePool;
- const android::ResStringPool* mSymbolPool;
- const Source mSource;
- IDiagnostics* mDiag;
+ if (pb_ref.has_symbol_idx()) {
+ const std::string str_symbol =
+ util::GetString(*symbol_pool_, pb_ref.symbol_idx());
+ ResourceNameRef name_ref;
+ if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) {
+ diag_->Error(DiagMessage(source_) << "invalid reference name '"
+ << str_symbol << "'");
+ return false;
+ }
+
+ out_ref->name = name_ref.ToResourceName();
+ }
+ return true;
+ }
+
+ template <typename T>
+ void DeserializeItemCommon(const T& pb_item, Value* out_value) {
+ if (pb_item.has_source()) {
+ Source source;
+ DeserializeSourceFromPb(pb_item.source(), *source_pool_, &source);
+ out_value->SetSource(std::move(source));
+ }
+
+ if (pb_item.has_comment()) {
+ out_value->SetComment(pb_item.comment());
+ }
+ }
+
+ private:
+ const android::ResStringPool* value_pool_;
+ const android::ResStringPool* source_pool_;
+ const android::ResStringPool* symbol_pool_;
+ const Source source_;
+ IDiagnostics* diag_;
};
-} // 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& pb_table, 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 (!pb_table.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 value_pool;
+ status_t result = value_pool.setTo(pb_table.string_pool().data().data(),
+ pb_table.string_pool().data().size());
+ if (result != NO_ERROR) {
+ diag->Error(DiagMessage(source) << "invalid string pool");
+ return {};
+ }
+
+ ResStringPool source_pool;
+ if (pb_table.has_source_pool()) {
+ result = source_pool.setTo(pb_table.source_pool().data().data(),
+ pb_table.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 symbol_pool;
+ if (pb_table.has_symbol_pool()) {
+ result = symbol_pool.setTo(pb_table.symbol_pool().data().data(),
+ pb_table.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 package_pb_deserializer(&value_pool, &source_pool,
+ &symbol_pool, source, diag);
+ for (const pb::Package& pb_package : pb_table.packages()) {
+ if (!package_pb_deserializer.DeserializeFromPb(pb_package, 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& pb_file, const Source& source, IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
- ResourceNameRef nameRef;
+ ResourceNameRef name_ref;
- // 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(pb_file.resource_name(), &name_ref)) {
+ diag->Error(DiagMessage(source)
+ << "invalid resource name in compiled file header: "
+ << pb_file.resource_name());
+ return {};
+ }
+ file->name = name_ref.ToResourceName();
+ file->source.path = pb_file.source_path();
+ DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config);
+
+ for (const pb::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbols()) {
+ // Need to create an lvalue here so that nameRef can point to something
+ // real.
+ if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(),
+ &name_ref)) {
+ diag->Error(DiagMessage(source)
+ << "invalid resource name for exported symbol in "
+ "compiled file header: "
+ << pb_file.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->exported_symbols.push_back(
+ SourcedResourceName{name_ref.ToResourceName(), pb_symbol.line_no()});
+ }
+ return file;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index a5c2cbc..68db6b3 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -22,6 +22,8 @@
#include "proto/ProtoSerialize.h"
#include "util/BigBuffer.h"
+#include <android-base/logging.h>
+
using google::protobuf::io::CodedOutputStream;
using google::protobuf::io::CodedInputStream;
using google::protobuf::io::ZeroCopyOutputStream;
@@ -31,179 +33,185 @@
namespace {
class PbSerializerVisitor : public RawValueVisitor {
-public:
- using RawValueVisitor::visit;
+ public:
+ using RawValueVisitor::Visit;
- /**
- * Constructor to use when expecting to serialize any value.
- */
- PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Value* outPbValue) :
- mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(outPbValue),
- mOutPbItem(nullptr) {
+ /**
+ * Constructor to use when expecting to serialize any value.
+ */
+ PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool,
+ pb::Value* out_pb_value)
+ : source_pool_(source_pool),
+ symbol_pool_(symbol_pool),
+ out_pb_value_(out_pb_value),
+ out_pb_item_(nullptr) {}
+
+ /**
+ * Constructor to use when expecting to serialize an Item.
+ */
+ PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool,
+ pb::Item* outPbItem)
+ : source_pool_(sourcePool),
+ symbol_pool_(symbolPool),
+ out_pb_value_(nullptr),
+ out_pb_item_(outPbItem) {}
+
+ void Visit(Reference* ref) override {
+ SerializeReferenceToPb(*ref, pb_item()->mutable_ref());
+ }
+
+ void Visit(String* str) override {
+ pb_item()->mutable_str()->set_idx(str->value.index());
+ }
+
+ void Visit(StyledString* str) override {
+ pb_item()->mutable_str()->set_idx(str->value.index());
+ }
+
+ void Visit(FileReference* file) override {
+ pb_item()->mutable_file()->set_path_idx(file->path.index());
+ }
+
+ void Visit(Id* id) override { pb_item()->mutable_id(); }
+
+ void Visit(RawString* raw_str) override {
+ pb_item()->mutable_raw_str()->set_idx(raw_str->value.index());
+ }
+
+ void Visit(BinaryPrimitive* prim) override {
+ android::Res_value val = {};
+ prim->Flatten(&val);
+
+ pb::Primitive* pb_prim = pb_item()->mutable_prim();
+ pb_prim->set_type(val.dataType);
+ pb_prim->set_data(val.data);
+ }
+
+ void VisitItem(Item* item) override { LOG(FATAL) << "unimplemented item"; }
+
+ void Visit(Attribute* attr) override {
+ pb::Attribute* pb_attr = pb_compound_value()->mutable_attr();
+ pb_attr->set_format_flags(attr->type_mask);
+ pb_attr->set_min_int(attr->min_int);
+ pb_attr->set_max_int(attr->max_int);
+
+ for (auto& symbol : attr->symbols) {
+ pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbols();
+ SerializeItemCommonToPb(symbol.symbol, pb_symbol);
+ SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());
+ pb_symbol->set_value(symbol.value);
+ }
+ }
+
+ void Visit(Style* style) override {
+ pb::Style* pb_style = pb_compound_value()->mutable_style();
+ if (style->parent) {
+ SerializeReferenceToPb(style->parent.value(), pb_style->mutable_parent());
+ SerializeSourceToPb(style->parent.value().GetSource(), source_pool_,
+ pb_style->mutable_parent_source());
}
- /**
- * Constructor to use when expecting to serialize an Item.
- */
- PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Item* outPbItem) :
- mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(nullptr),
- mOutPbItem(outPbItem) {
+ for (Style::Entry& entry : style->entries) {
+ pb::Style_Entry* pb_entry = pb_style->add_entries();
+ SerializeReferenceToPb(entry.key, pb_entry->mutable_key());
+
+ pb::Item* pb_item = pb_entry->mutable_item();
+ SerializeItemCommonToPb(entry.key, pb_entry);
+ PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item);
+ entry.value->Accept(&sub_visitor);
+ }
+ }
+
+ void Visit(Styleable* styleable) override {
+ pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable();
+ for (Reference& entry : styleable->entries) {
+ pb::Styleable_Entry* pb_entry = pb_styleable->add_entries();
+ SerializeItemCommonToPb(entry, pb_entry);
+ SerializeReferenceToPb(entry, pb_entry->mutable_attr());
+ }
+ }
+
+ void Visit(Array* array) override {
+ pb::Array* pb_array = pb_compound_value()->mutable_array();
+ for (auto& value : array->items) {
+ pb::Array_Entry* pb_entry = pb_array->add_entries();
+ SerializeItemCommonToPb(*value, pb_entry);
+ PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_,
+ pb_entry->mutable_item());
+ value->Accept(&sub_visitor);
+ }
+ }
+
+ void Visit(Plural* plural) override {
+ pb::Plural* pb_plural = pb_compound_value()->mutable_plural();
+ const size_t count = plural->values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (!plural->values[i]) {
+ // No plural value set here.
+ continue;
+ }
+
+ pb::Plural_Entry* pb_entry = pb_plural->add_entries();
+ pb_entry->set_arity(SerializePluralEnumToPb(i));
+ pb::Item* pb_element = pb_entry->mutable_item();
+ SerializeItemCommonToPb(*plural->values[i], pb_entry);
+ PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element);
+ plural->values[i]->Accept(&sub_visitor);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PbSerializerVisitor);
+
+ pb::Item* pb_item() {
+ if (out_pb_value_) {
+ return out_pb_value_->mutable_item();
+ }
+ return out_pb_item_;
+ }
+
+ pb::CompoundValue* pb_compound_value() {
+ CHECK(out_pb_value_ != nullptr);
+ return out_pb_value_->mutable_compound_value();
+ }
+
+ template <typename T>
+ void SerializeItemCommonToPb(const Item& item, T* pb_item) {
+ SerializeSourceToPb(item.GetSource(), source_pool_,
+ pb_item->mutable_source());
+ if (!item.GetComment().empty()) {
+ pb_item->set_comment(item.GetComment());
+ }
+ }
+
+ void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) {
+ if (ref.id) {
+ pb_ref->set_id(ref.id.value().id);
}
- void visit(Reference* ref) override {
- serializeReferenceToPb(*ref, getPbItem()->mutable_ref());
+ if (ref.name) {
+ StringPool::Ref symbol_ref =
+ symbol_pool_->MakeRef(ref.name.value().ToString());
+ pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index()));
}
- void visit(String* str) override {
- getPbItem()->mutable_str()->set_idx(str->value.getIndex());
- }
+ pb_ref->set_private_(ref.private_reference);
+ pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+ }
- void visit(StyledString* str) override {
- getPbItem()->mutable_str()->set_idx(str->value.getIndex());
- }
-
- void visit(FileReference* file) override {
- getPbItem()->mutable_file()->set_path_idx(file->path.getIndex());
- }
-
- void visit(Id* id) override {
- getPbItem()->mutable_id();
- }
-
- void visit(RawString* rawStr) override {
- getPbItem()->mutable_raw_str()->set_idx(rawStr->value.getIndex());
- }
-
- void visit(BinaryPrimitive* prim) override {
- android::Res_value val = {};
- prim->flatten(&val);
-
- pb::Primitive* pbPrim = getPbItem()->mutable_prim();
- pbPrim->set_type(val.dataType);
- pbPrim->set_data(val.data);
- }
-
- void visitItem(Item* item) override {
- assert(false && "unimplemented item");
- }
-
- void visit(Attribute* attr) override {
- pb::Attribute* pbAttr = getPbCompoundValue()->mutable_attr();
- pbAttr->set_format_flags(attr->typeMask);
- pbAttr->set_min_int(attr->minInt);
- pbAttr->set_max_int(attr->maxInt);
-
- for (auto& symbol : attr->symbols) {
- pb::Attribute_Symbol* pbSymbol = pbAttr->add_symbols();
- serializeItemCommonToPb(symbol.symbol, pbSymbol);
- serializeReferenceToPb(symbol.symbol, pbSymbol->mutable_name());
- pbSymbol->set_value(symbol.value);
- }
- }
-
- void visit(Style* style) override {
- pb::Style* pbStyle = getPbCompoundValue()->mutable_style();
- if (style->parent) {
- serializeReferenceToPb(style->parent.value(), pbStyle->mutable_parent());
- serializeSourceToPb(style->parent.value().getSource(),
- mSourcePool,
- pbStyle->mutable_parent_source());
- }
-
- for (Style::Entry& entry : style->entries) {
- pb::Style_Entry* pbEntry = pbStyle->add_entries();
- serializeReferenceToPb(entry.key, pbEntry->mutable_key());
-
- pb::Item* pbItem = pbEntry->mutable_item();
- serializeItemCommonToPb(entry.key, pbEntry);
- PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem);
- entry.value->accept(&subVisitor);
- }
- }
-
- void visit(Styleable* styleable) override {
- pb::Styleable* pbStyleable = getPbCompoundValue()->mutable_styleable();
- for (Reference& entry : styleable->entries) {
- pb::Styleable_Entry* pbEntry = pbStyleable->add_entries();
- serializeItemCommonToPb(entry, pbEntry);
- serializeReferenceToPb(entry, pbEntry->mutable_attr());
- }
- }
-
- void visit(Array* array) override {
- pb::Array* pbArray = getPbCompoundValue()->mutable_array();
- for (auto& value : array->items) {
- pb::Array_Entry* pbEntry = pbArray->add_entries();
- serializeItemCommonToPb(*value, pbEntry);
- PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbEntry->mutable_item());
- value->accept(&subVisitor);
- }
- }
-
- void visit(Plural* plural) override {
- pb::Plural* pbPlural = getPbCompoundValue()->mutable_plural();
- const size_t count = plural->values.size();
- for (size_t i = 0; i < count; i++) {
- if (!plural->values[i]) {
- // No plural value set here.
- continue;
- }
-
- pb::Plural_Entry* pbEntry = pbPlural->add_entries();
- pbEntry->set_arity(serializePluralEnumToPb(i));
- pb::Item* pbElement = pbEntry->mutable_item();
- serializeItemCommonToPb(*plural->values[i], pbEntry);
- PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbElement);
- plural->values[i]->accept(&subVisitor);
- }
- }
-
-private:
- pb::Item* getPbItem() {
- if (mOutPbValue) {
- return mOutPbValue->mutable_item();
- }
- return mOutPbItem;
- }
-
- pb::CompoundValue* getPbCompoundValue() {
- assert(mOutPbValue);
- return mOutPbValue->mutable_compound_value();
- }
-
- template <typename T>
- void serializeItemCommonToPb(const Item& item, T* pbItem) {
- serializeSourceToPb(item.getSource(), mSourcePool, pbItem->mutable_source());
- if (!item.getComment().empty()) {
- pbItem->set_comment(item.getComment());
- }
- }
-
- void serializeReferenceToPb(const Reference& ref, pb::Reference* pbRef) {
- if (ref.id) {
- pbRef->set_id(ref.id.value().id);
- }
-
- if (ref.name) {
- StringPool::Ref symbolRef = mSymbolPool->makeRef(ref.name.value().toString());
- pbRef->set_symbol_idx(static_cast<uint32_t>(symbolRef.getIndex()));
- }
-
- pbRef->set_private_(ref.privateReference);
- pbRef->set_type(serializeReferenceTypeToPb(ref.referenceType));
- }
-
- StringPool* mSourcePool;
- StringPool* mSymbolPool;
- pb::Value* mOutPbValue;
- pb::Item* mOutPbItem;
+ StringPool* source_pool_;
+ StringPool* symbol_pool_;
+ pb::Value* out_pb_value_;
+ pb::Item* out_pb_item_;
};
-} // namespace
+} // namespace
-std::unique_ptr<pb::ResourceTable> serializeTableToPb(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 {
+std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
+ // We must do this before writing the resources, since the string pool IDs may
+ // change.
+ table->string_pool.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;
@@ -211,192 +219,195 @@
if (diff < 0) return true;
if (diff > 0) return false;
return a.value < b.value;
- });
- table->stringPool.prune();
+ });
+ table->string_pool.Prune();
- auto pbTable = util::make_unique<pb::ResourceTable>();
- serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool());
+ auto pb_table = util::make_unique<pb::ResourceTable>();
+ SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool());
- StringPool sourcePool, symbolPool;
+ StringPool source_pool, symbol_pool;
- for (auto& package : table->packages) {
- pb::Package* pbPackage = pbTable->add_packages();
- if (package->id) {
- pbPackage->set_package_id(package->id.value());
+ for (auto& package : table->packages) {
+ pb::Package* pb_package = pb_table->add_packages();
+ if (package->id) {
+ pb_package->set_package_id(package->id.value());
+ }
+ pb_package->set_package_name(package->name);
+
+ for (auto& type : package->types) {
+ pb::Type* pb_type = pb_package->add_types();
+ if (type->id) {
+ pb_type->set_id(type->id.value());
+ }
+ pb_type->set_name(ToString(type->type).ToString());
+
+ for (auto& entry : type->entries) {
+ pb::Entry* pb_entry = pb_type->add_entries();
+ if (entry->id) {
+ pb_entry->set_id(entry->id.value());
}
- pbPackage->set_package_name(package->name);
+ pb_entry->set_name(entry->name);
- for (auto& type : package->types) {
- pb::Type* pbType = pbPackage->add_types();
- if (type->id) {
- pbType->set_id(type->id.value());
- }
- pbType->set_name(toString(type->type).toString());
+ // Write the SymbolStatus struct.
+ pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
+ pb_status->set_visibility(
+ SerializeVisibilityToPb(entry->symbol_status.state));
+ SerializeSourceToPb(entry->symbol_status.source, &source_pool,
+ pb_status->mutable_source());
+ pb_status->set_comment(entry->symbol_status.comment);
- for (auto& entry : type->entries) {
- pb::Entry* pbEntry = pbType->add_entries();
- if (entry->id) {
- pbEntry->set_id(entry->id.value());
- }
- pbEntry->set_name(entry->name);
+ for (auto& config_value : entry->values) {
+ pb::ConfigValue* pb_config_value = pb_entry->add_config_values();
+ SerializeConfig(config_value->config,
+ pb_config_value->mutable_config());
+ if (!config_value->product.empty()) {
+ pb_config_value->mutable_config()->set_product(
+ config_value->product);
+ }
- // Write the SymbolStatus struct.
- pb::SymbolStatus* pbStatus = pbEntry->mutable_symbol_status();
- pbStatus->set_visibility(serializeVisibilityToPb(entry->symbolStatus.state));
- serializeSourceToPb(entry->symbolStatus.source, &sourcePool,
- pbStatus->mutable_source());
- pbStatus->set_comment(entry->symbolStatus.comment);
+ pb::Value* pb_value = pb_config_value->mutable_value();
+ SerializeSourceToPb(config_value->value->GetSource(), &source_pool,
+ pb_value->mutable_source());
+ if (!config_value->value->GetComment().empty()) {
+ pb_value->set_comment(config_value->value->GetComment());
+ }
- for (auto& configValue : entry->values) {
- pb::ConfigValue* pbConfigValue = pbEntry->add_config_values();
- serializeConfig(configValue->config, pbConfigValue->mutable_config());
- if (!configValue->product.empty()) {
- pbConfigValue->mutable_config()->set_product(configValue->product);
- }
+ if (config_value->value->IsWeak()) {
+ pb_value->set_weak(true);
+ }
- pb::Value* pbValue = pbConfigValue->mutable_value();
- serializeSourceToPb(configValue->value->getSource(), &sourcePool,
- pbValue->mutable_source());
- if (!configValue->value->getComment().empty()) {
- pbValue->set_comment(configValue->value->getComment());
- }
-
- if (configValue->value->isWeak()) {
- pbValue->set_weak(true);
- }
-
- PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue);
- configValue->value->accept(&visitor);
- }
- }
+ PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value);
+ config_value->value->Accept(&visitor);
}
+ }
}
+ }
- serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool());
- serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool());
- return pbTable;
+ SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool());
+ SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool());
+ return pb_table;
}
-std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) {
- auto pbFile = util::make_unique<pb::CompiledFile>();
- pbFile->set_resource_name(file.name.toString());
- pbFile->set_source_path(file.source.path);
- serializeConfig(file.config, pbFile->mutable_config());
+std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb(
+ const ResourceFile& file) {
+ auto pb_file = util::make_unique<pb::CompiledFile>();
+ pb_file->set_resource_name(file.name.ToString());
+ pb_file->set_source_path(file.source.path);
+ SerializeConfig(file.config, pb_file->mutable_config());
- for (const SourcedResourceName& exported : file.exportedSymbols) {
- pb::CompiledFile_Symbol* pbSymbol = pbFile->add_exported_symbols();
- pbSymbol->set_resource_name(exported.name.toString());
- pbSymbol->set_line_no(exported.line);
- }
- return pbFile;
+ for (const SourcedResourceName& exported : file.exported_symbols) {
+ pb::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbols();
+ pb_symbol->set_resource_name(exported.name.ToString());
+ pb_symbol->set_line_no(exported.line);
+ }
+ return pb_file;
}
-CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : mOut(out) {
-}
+CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out)
+ : out_(out) {}
-void CompiledFileOutputStream::ensureAlignedWrite() {
- const int padding = mOut.ByteCount() % 4;
- if (padding > 0) {
- uint32_t zero = 0u;
- mOut.WriteRaw(&zero, padding);
- }
+void CompiledFileOutputStream::EnsureAlignedWrite() {
+ const int padding = out_.ByteCount() % 4;
+ if (padding > 0) {
+ uint32_t zero = 0u;
+ out_.WriteRaw(&zero, padding);
+ }
}
void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {
- ensureAlignedWrite();
- mOut.WriteLittleEndian32(val);
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian32(val);
}
-void CompiledFileOutputStream::WriteCompiledFile(const pb::CompiledFile* compiledFile) {
- ensureAlignedWrite();
- mOut.WriteLittleEndian64(static_cast<uint64_t>(compiledFile->ByteSize()));
- compiledFile->SerializeWithCachedSizes(&mOut);
+void CompiledFileOutputStream::WriteCompiledFile(
+ const pb::CompiledFile* compiled_file) {
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize()));
+ compiled_file->SerializeWithCachedSizes(&out_);
}
void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) {
- ensureAlignedWrite();
- mOut.WriteLittleEndian64(static_cast<uint64_t>(buffer->size()));
- for (const BigBuffer::Block& block : *buffer) {
- mOut.WriteRaw(block.buffer.get(), block.size);
- }
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian64(static_cast<uint64_t>(buffer->size()));
+ for (const BigBuffer::Block& block : *buffer) {
+ out_.WriteRaw(block.buffer.get(), block.size);
+ }
}
void CompiledFileOutputStream::WriteData(const void* data, size_t len) {
- ensureAlignedWrite();
- mOut.WriteLittleEndian64(static_cast<uint64_t>(len));
- mOut.WriteRaw(data, len);
+ EnsureAlignedWrite();
+ out_.WriteLittleEndian64(static_cast<uint64_t>(len));
+ out_.WriteRaw(data, len);
}
-bool CompiledFileOutputStream::HadError() {
- return mOut.HadError();
+bool CompiledFileOutputStream::HadError() { return out_.HadError(); }
+
+CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size)
+ : in_(static_cast<const uint8_t*>(data), size) {}
+
+void CompiledFileInputStream::EnsureAlignedRead() {
+ const int padding = in_.CurrentPosition() % 4;
+ if (padding > 0) {
+ // Reads are always 4 byte aligned.
+ in_.Skip(padding);
+ }
}
-CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) :
- mIn(static_cast<const uint8_t*>(data), size) {
+bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) {
+ EnsureAlignedRead();
+ return in_.ReadLittleEndian32(out_val);
}
-void CompiledFileInputStream::ensureAlignedRead() {
- const int padding = mIn.CurrentPosition() % 4;
- if (padding > 0) {
- // Reads are always 4 byte aligned.
- mIn.Skip(padding);
- }
+bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) {
+ EnsureAlignedRead();
+
+ uint64_t pb_size = 0u;
+ if (!in_.ReadLittleEndian64(&pb_size)) {
+ return false;
+ }
+
+ CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size));
+
+ // Check that we haven't tried to read past the end.
+ if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) {
+ in_.PopLimit(l);
+ in_.PushLimit(0);
+ return false;
+ }
+
+ if (!out_val->ParsePartialFromCodedStream(&in_)) {
+ in_.PopLimit(l);
+ in_.PushLimit(0);
+ return false;
+ }
+
+ in_.PopLimit(l);
+ return true;
}
-bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* outVal) {
- ensureAlignedRead();
- return mIn.ReadLittleEndian32(outVal);
+bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset,
+ uint64_t* out_len) {
+ EnsureAlignedRead();
+
+ uint64_t pb_size = 0u;
+ if (!in_.ReadLittleEndian64(&pb_size)) {
+ return false;
+ }
+
+ // Check that we aren't trying to read past the end.
+ if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) {
+ in_.PushLimit(0);
+ return false;
+ }
+
+ uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition());
+ if (!in_.Skip(pb_size)) {
+ return false;
+ }
+
+ *out_offset = offset;
+ *out_len = pb_size;
+ return true;
}
-bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* outVal) {
- ensureAlignedRead();
-
- uint64_t pbSize = 0u;
- if (!mIn.ReadLittleEndian64(&pbSize)) {
- return false;
- }
-
- CodedInputStream::Limit l = mIn.PushLimit(static_cast<int>(pbSize));
-
- // Check that we haven't tried to read past the end.
- if (static_cast<uint64_t>(mIn.BytesUntilLimit()) != pbSize) {
- mIn.PopLimit(l);
- mIn.PushLimit(0);
- return false;
- }
-
- if (!outVal->ParsePartialFromCodedStream(&mIn)) {
- mIn.PopLimit(l);
- mIn.PushLimit(0);
- return false;
- }
-
- mIn.PopLimit(l);
- return true;
-}
-
-bool CompiledFileInputStream::ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen) {
- ensureAlignedRead();
-
- uint64_t pbSize = 0u;
- if (!mIn.ReadLittleEndian64(&pbSize)) {
- return false;
- }
-
- // Check that we aren't trying to read past the end.
- if (pbSize > static_cast<uint64_t>(mIn.BytesUntilLimit())) {
- mIn.PushLimit(0);
- return false;
- }
-
- uint64_t offset = static_cast<uint64_t>(mIn.CurrentPosition());
- if (!mIn.Skip(pbSize)) {
- return false;
- }
-
- *outOffset = offset;
- *outLen = pbSize;
- return true;
-}
-
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
index 2bd9767..fdd5197 100644
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -14,200 +14,211 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "proto/ProtoSerialize.h"
+
+#include "ResourceTable.h"
#include "test/Test.h"
-using namespace google::protobuf::io;
+using ::google::protobuf::io::StringOutputStream;
namespace aapt {
TEST(TableProtoSerializer, SerializeSinglePackage) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId("com.app.a", 0x7f)
- .addFileReference("com.app.a:layout/main", ResourceId(0x7f020000),
- "res/layout/main.xml")
- .addReference("com.app.a:layout/other", ResourceId(0x7f020001),
- "com.app.a:layout/main")
- .addString("com.app.a:string/text", {}, "hi")
- .addValue("com.app.a:id/foo", {}, util::make_unique<Id>())
- .build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000),
+ "res/layout/main.xml")
+ .AddReference("com.app.a:layout/other", ResourceId(0x7f020001),
+ "com.app.a:layout/main")
+ .AddString("com.app.a:string/text", {}, "hi")
+ .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
+ .Build();
- Symbol publicSymbol;
- publicSymbol.state = SymbolState::kPublic;
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie("com.app.a:layout/main"),
- ResourceId(0x7f020000),
- publicSymbol, context->getDiagnostics()));
+ Symbol public_symbol;
+ public_symbol.state = SymbolState::kPublic;
+ ASSERT_TRUE(table->SetSymbolState(
+ test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000),
+ public_symbol, context->GetDiagnostics()));
- Id* id = test::getValue<Id>(table.get(), "com.app.a:id/foo");
- ASSERT_NE(nullptr, id);
+ Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
+ ASSERT_NE(nullptr, id);
- // Make a plural.
- std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef("one"));
- ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:plurals/hey"),
- ConfigDescription{}, {}, std::move(plural),
- context->getDiagnostics()));
+ // Make a plural.
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ plural->values[Plural::One] =
+ util::make_unique<String>(table->string_pool.MakeRef("one"));
+ ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"),
+ ConfigDescription{}, {}, std::move(plural),
+ context->GetDiagnostics()));
- // Make a resource with different products.
- ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:integer/one"),
- test::parseConfigOrDie("land"), {},
- test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
- context->getDiagnostics()));
- ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:integer/one"),
- test::parseConfigOrDie("land"), "tablet",
- test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
- context->getDiagnostics()));
+ // Make a resource with different products.
+ ASSERT_TRUE(table->AddResource(
+ test::ParseNameOrDie("com.app.a:integer/one"),
+ test::ParseConfigOrDie("land"), {},
+ test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table->AddResource(
+ test::ParseNameOrDie("com.app.a:integer/one"),
+ test::ParseConfigOrDie("land"), "tablet",
+ test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
+ context->GetDiagnostics()));
- // Make a reference with both resource name and resource ID.
- // The reference should point to a resource outside of this table to test that both
- // name and id get serialized.
- Reference expectedRef;
- expectedRef.name = test::parseNameOrDie("android:layout/main");
- expectedRef.id = ResourceId(0x01020000);
- ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:layout/abc"),
- ConfigDescription::defaultConfig(), {},
- util::make_unique<Reference>(expectedRef),
- context->getDiagnostics()));
+ // Make a reference with both resource name and resource ID.
+ // The reference should point to a resource outside of this table to test that
+ // both
+ // name and id get serialized.
+ Reference expected_ref;
+ expected_ref.name = test::ParseNameOrDie("android:layout/main");
+ expected_ref.id = ResourceId(0x01020000);
+ ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:layout/abc"),
+ ConfigDescription::DefaultConfig(), {},
+ util::make_unique<Reference>(expected_ref),
+ context->GetDiagnostics()));
- std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get());
- ASSERT_NE(nullptr, pbTable);
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get());
+ ASSERT_NE(nullptr, pb_table);
- std::unique_ptr<ResourceTable> newTable = deserializeTableFromPb(*pbTable,
- Source{ "test" },
- context->getDiagnostics());
- ASSERT_NE(nullptr, newTable);
+ std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb(
+ *pb_table, Source{"test"}, context->GetDiagnostics());
+ ASSERT_NE(nullptr, new_table);
- Id* newId = test::getValue<Id>(newTable.get(), "com.app.a:id/foo");
- ASSERT_NE(nullptr, newId);
- EXPECT_EQ(id->isWeak(), newId->isWeak());
+ Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo");
+ ASSERT_NE(nullptr, new_id);
+ EXPECT_EQ(id->IsWeak(), new_id->IsWeak());
- Maybe<ResourceTable::SearchResult> result = newTable->findResource(
- test::parseNameOrDie("com.app.a:layout/main"));
- AAPT_ASSERT_TRUE(result);
- EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state);
- EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+ Maybe<ResourceTable::SearchResult> result =
+ new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
- // Find the product-dependent values
- BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
- newTable.get(), "com.app.a:integer/one", test::parseConfigOrDie("land"), "");
- ASSERT_NE(nullptr, prim);
- EXPECT_EQ(123u, prim->value.data);
+ // Find the product-dependent values
+ BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"),
+ "");
+ ASSERT_NE(nullptr, prim);
+ EXPECT_EQ(123u, prim->value.data);
- prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
- newTable.get(), "com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet");
- ASSERT_NE(nullptr, prim);
- EXPECT_EQ(321u, prim->value.data);
+ prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+ new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"),
+ "tablet");
+ ASSERT_NE(nullptr, prim);
+ EXPECT_EQ(321u, prim->value.data);
- Reference* actualRef = test::getValue<Reference>(newTable.get(), "com.app.a:layout/abc");
- ASSERT_NE(nullptr, actualRef);
- AAPT_ASSERT_TRUE(actualRef->name);
- AAPT_ASSERT_TRUE(actualRef->id);
- EXPECT_EQ(expectedRef.name.value(), actualRef->name.value());
- EXPECT_EQ(expectedRef.id.value(), actualRef->id.value());
+ Reference* actual_ref =
+ test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc");
+ ASSERT_NE(nullptr, actual_ref);
+ AAPT_ASSERT_TRUE(actual_ref->name);
+ AAPT_ASSERT_TRUE(actual_ref->id);
+ EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value());
+ EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value());
}
TEST(TableProtoSerializer, SerializeFileHeader) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- ResourceFile f;
- f.config = test::parseConfigOrDie("hdpi-v9");
- f.name = test::parseNameOrDie("com.app.a:layout/main");
- f.source.path = "res/layout-hdpi-v9/main.xml";
- f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie("id/unchecked"), 23u });
+ ResourceFile f;
+ f.config = test::ParseConfigOrDie("hdpi-v9");
+ f.name = test::ParseNameOrDie("com.app.a:layout/main");
+ f.source.path = "res/layout-hdpi-v9/main.xml";
+ f.exported_symbols.push_back(
+ SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u});
- const std::string expectedData1 = "123";
- const std::string expectedData2 = "1234";
+ const std::string expected_data1 = "123";
+ const std::string expected_data2 = "1234";
- std::string outputStr;
- {
- std::unique_ptr<pb::CompiledFile> pbFile1 = serializeCompiledFileToPb(f);
+ std::string output_str;
+ {
+ std::unique_ptr<pb::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f);
- f.name.entry = "__" + f.name.entry + "$0";
- std::unique_ptr<pb::CompiledFile> pbFile2 = serializeCompiledFileToPb(f);
+ f.name.entry = "__" + f.name.entry + "$0";
+ std::unique_ptr<pb::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f);
- StringOutputStream outStream(&outputStr);
- CompiledFileOutputStream outFileStream(&outStream);
- outFileStream.WriteLittleEndian32(2);
- outFileStream.WriteCompiledFile(pbFile1.get());
- outFileStream.WriteData(expectedData1.data(), expectedData1.size());
- outFileStream.WriteCompiledFile(pbFile2.get());
- outFileStream.WriteData(expectedData2.data(), expectedData2.size());
- ASSERT_FALSE(outFileStream.HadError());
- }
+ StringOutputStream out_stream(&output_str);
+ CompiledFileOutputStream out_file_stream(&out_stream);
+ out_file_stream.WriteLittleEndian32(2);
+ out_file_stream.WriteCompiledFile(pb_file1.get());
+ out_file_stream.WriteData(expected_data1.data(), expected_data1.size());
+ out_file_stream.WriteCompiledFile(pb_file2.get());
+ out_file_stream.WriteData(expected_data2.data(), expected_data2.size());
+ ASSERT_FALSE(out_file_stream.HadError());
+ }
- CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
- uint32_t numFiles = 0;
- ASSERT_TRUE(inFileStream.ReadLittleEndian32(&numFiles));
- ASSERT_EQ(2u, numFiles);
+ CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
+ uint32_t num_files = 0;
+ ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
+ ASSERT_EQ(2u, num_files);
- // Read the first compiled file.
+ // Read the first compiled file.
- pb::CompiledFile newPbFile;
- ASSERT_TRUE(inFileStream.ReadCompiledFile(&newPbFile));
+ pb::CompiledFile new_pb_file;
+ ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));
- std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(newPbFile, Source("test"),
- context->getDiagnostics());
- ASSERT_NE(nullptr, file);
+ std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb(
+ new_pb_file, Source("test"), context->GetDiagnostics());
+ ASSERT_NE(nullptr, file);
- uint64_t offset, len;
- ASSERT_TRUE(inFileStream.ReadDataMetaData(&offset, &len));
+ uint64_t offset, len;
+ ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
- std::string actualData(outputStr.data() + offset, len);
- EXPECT_EQ(expectedData1, actualData);
+ std::string actual_data(output_str.data() + offset, len);
+ EXPECT_EQ(expected_data1, actual_data);
- // Expect the data to be aligned.
- EXPECT_EQ(0u, offset & 0x03);
+ // Expect the data to be aligned.
+ EXPECT_EQ(0u, offset & 0x03);
- ASSERT_EQ(1u, file->exportedSymbols.size());
- EXPECT_EQ(test::parseNameOrDie("id/unchecked"), file->exportedSymbols[0].name);
+ ASSERT_EQ(1u, file->exported_symbols.size());
+ EXPECT_EQ(test::ParseNameOrDie("id/unchecked"),
+ file->exported_symbols[0].name);
- // Read the second compiled file.
+ // Read the second compiled file.
- ASSERT_TRUE(inFileStream.ReadCompiledFile(&newPbFile));
+ ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));
- file = deserializeCompiledFileFromPb(newPbFile, Source("test"), context->getDiagnostics());
- ASSERT_NE(nullptr, file);
+ file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"),
+ context->GetDiagnostics());
+ ASSERT_NE(nullptr, file);
- ASSERT_TRUE(inFileStream.ReadDataMetaData(&offset, &len));
+ ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
- actualData = std::string(outputStr.data() + offset, len);
- EXPECT_EQ(expectedData2, actualData);
+ actual_data = std::string(output_str.data() + offset, len);
+ EXPECT_EQ(expected_data2, actual_data);
- // Expect the data to be aligned.
- EXPECT_EQ(0u, offset & 0x03);
+ // Expect the data to be aligned.
+ EXPECT_EQ(0u, offset & 0x03);
}
TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {
- ResourceFile f;
- std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+ ResourceFile f;
+ std::unique_ptr<pb::CompiledFile> pb_file = SerializeCompiledFileToPb(f);
- const std::string expectedData = "1234";
+ const std::string expected_data = "1234";
- std::string outputStr;
- {
- StringOutputStream outStream(&outputStr);
- CompiledFileOutputStream outFileStream(&outStream);
- outFileStream.WriteLittleEndian32(1);
- outFileStream.WriteCompiledFile(pbFile.get());
- outFileStream.WriteData(expectedData.data(), expectedData.size());
- ASSERT_FALSE(outFileStream.HadError());
- }
+ std::string output_str;
+ {
+ StringOutputStream out_stream(&output_str);
+ CompiledFileOutputStream out_file_stream(&out_stream);
+ out_file_stream.WriteLittleEndian32(1);
+ out_file_stream.WriteCompiledFile(pb_file.get());
+ out_file_stream.WriteData(expected_data.data(), expected_data.size());
+ ASSERT_FALSE(out_file_stream.HadError());
+ }
- outputStr[4] = 0xff;
+ output_str[4] = 0xff;
- CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
+ CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
- uint32_t numFiles = 0;
- EXPECT_TRUE(inFileStream.ReadLittleEndian32(&numFiles));
- EXPECT_EQ(1u, numFiles);
+ uint32_t num_files = 0;
+ EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
+ EXPECT_EQ(1u, num_files);
- pb::CompiledFile newPbFile;
- EXPECT_FALSE(inFileStream.ReadCompiledFile(&newPbFile));
+ pb::CompiledFile new_pb_file;
+ EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file));
- uint64_t offset, len;
- EXPECT_FALSE(inFileStream.ReadDataMetaData(&offset, &len));
+ uint64_t offset, len;
+ EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 267200d..93c790d 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -4,6 +4,10 @@
### `aapt2 compile ...`
- Added support for inline complex XML resources. See
https://developer.android.com/guide/topics/resources/complex-xml-resources.html
+### `aapt link ...`
+- Duplicate resource filtering: removes duplicate resources in dominated configurations
+ that are always identical when selected at runtime. This can be disabled with
+ `--no-resource-deduping`.
## Version 2.1
### `aapt2 link ...`
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 08b9ee9..7aad86f 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -14,251 +14,278 @@
* limitations under the License.
*/
-#include "ConfigDescription.h"
-#include "ResourceTable.h"
#include "split/TableSplitter.h"
-#include "util/Util.h"
#include <algorithm>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>
+#include "android-base/logging.h"
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "util/Util.h"
namespace aapt {
using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
-using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
+using ConfigDensityGroups =
+ std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
-static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
- ConfigDescription withoutDensity = config;
- withoutDensity.density = 0;
- return withoutDensity;
+static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) {
+ ConfigDescription without_density = config;
+ without_density.density = 0;
+ return without_density;
}
/**
* Selects values that match exactly the constraints given.
*/
class SplitValueSelector {
-public:
- explicit SplitValueSelector(const SplitConstraints& constraints) {
- for (const ConfigDescription& config : constraints.configs) {
- if (config.density == 0) {
- mDensityIndependentConfigs.insert(config);
- } else {
- mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
- }
+ public:
+ explicit SplitValueSelector(const SplitConstraints& constraints) {
+ for (const ConfigDescription& config : constraints.configs) {
+ if (config.density == 0) {
+ density_independent_configs_.insert(config);
+ } else {
+ density_dependent_config_to_density_map_[CopyWithoutDensity(config)] =
+ config.density;
+ }
+ }
+ }
+
+ std::vector<ResourceConfigValue*> SelectValues(
+ const ConfigDensityGroups& density_groups,
+ ConfigClaimedMap* claimed_values) {
+ std::vector<ResourceConfigValue*> selected;
+
+ // Select the regular values.
+ for (auto& entry : *claimed_values) {
+ // Check if the entry has a density.
+ ResourceConfigValue* config_value = entry.first;
+ if (config_value->config.density == 0 && !entry.second) {
+ // This is still available.
+ if (density_independent_configs_.find(config_value->config) !=
+ density_independent_configs_.end()) {
+ selected.push_back(config_value);
+
+ // Mark the entry as taken.
+ entry.second = true;
}
+ }
}
- std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
- ConfigClaimedMap* claimedValues) {
- std::vector<ResourceConfigValue*> selected;
+ // Now examine the densities
+ for (auto& entry : density_groups) {
+ // We do not care if the value is claimed, since density values can be
+ // in multiple splits.
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& related_values = entry.second;
+ auto density_value_iter =
+ density_dependent_config_to_density_map_.find(config);
+ if (density_value_iter !=
+ density_dependent_config_to_density_map_.end()) {
+ // Select the best one!
+ ConfigDescription target_density = config;
+ target_density.density = density_value_iter->second;
- // Select the regular values.
- for (auto& entry : *claimedValues) {
- // Check if the entry has a density.
- ResourceConfigValue* configValue = entry.first;
- if (configValue->config.density == 0 && !entry.second) {
- // This is still available.
- if (mDensityIndependentConfigs.find(configValue->config) !=
- mDensityIndependentConfigs.end()) {
- selected.push_back(configValue);
-
- // Mark the entry as taken.
- entry.second = true;
- }
- }
+ ResourceConfigValue* best_value = nullptr;
+ for (ResourceConfigValue* this_value : related_values) {
+ if (!best_value ||
+ this_value->config.isBetterThan(best_value->config,
+ &target_density)) {
+ best_value = this_value;
+ }
}
+ CHECK(best_value != nullptr);
- // Now examine the densities
- for (auto& entry : densityGroups) {
- // We do not care if the value is claimed, since density values can be
- // in multiple splits.
- const ConfigDescription& config = entry.first;
- const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
- auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
- if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
- // Select the best one!
- ConfigDescription targetDensity = config;
- targetDensity.density = densityValueIter->second;
-
- ResourceConfigValue* bestValue = nullptr;
- for (ResourceConfigValue* thisValue : relatedValues) {
- if (!bestValue ||
- thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
- bestValue = thisValue;
- }
- }
- assert(bestValue);
-
- // When we select one of these, they are all claimed such that the base
- // doesn't include any anymore.
- (*claimedValues)[bestValue] = true;
- selected.push_back(bestValue);
- }
- }
- return selected;
+ // When we select one of these, they are all claimed such that the base
+ // doesn't include any anymore.
+ (*claimed_values)[best_value] = true;
+ selected.push_back(best_value);
+ }
}
+ return selected;
+ }
-private:
- std::set<ConfigDescription> mDensityIndependentConfigs;
- std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SplitValueSelector);
+
+ std::set<ConfigDescription> density_independent_configs_;
+ std::map<ConfigDescription, uint16_t>
+ density_dependent_config_to_density_map_;
};
/**
- * Marking non-preferred densities as claimed will make sure the base doesn't include them,
+ * Marking non-preferred densities as claimed will make sure the base doesn't
+ * include them,
* leaving only the preferred density behind.
*/
-static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
- const ConfigDensityGroups& densityGroups,
- ConfigClaimedMap* configClaimedMap) {
- for (auto& entry : densityGroups) {
- const ConfigDescription& config = entry.first;
- const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+static void MarkNonPreferredDensitiesAsClaimed(
+ uint16_t preferred_density, const ConfigDensityGroups& density_groups,
+ ConfigClaimedMap* config_claimed_map) {
+ for (auto& entry : density_groups) {
+ const ConfigDescription& config = entry.first;
+ const std::vector<ResourceConfigValue*>& related_values = entry.second;
- ConfigDescription targetDensity = config;
- targetDensity.density = preferredDensity;
- ResourceConfigValue* bestValue = nullptr;
- for (ResourceConfigValue* thisValue : relatedValues) {
- if (!bestValue) {
- bestValue = thisValue;
- } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
- // Claim the previous value so that it is not included in the base.
- (*configClaimedMap)[bestValue] = true;
- bestValue = thisValue;
- } else {
- // Claim this value so that it is not included in the base.
- (*configClaimedMap)[thisValue] = true;
- }
- }
- assert(bestValue);
+ ConfigDescription target_density = config;
+ target_density.density = preferred_density;
+ ResourceConfigValue* best_value = nullptr;
+ for (ResourceConfigValue* this_value : related_values) {
+ if (!best_value) {
+ best_value = this_value;
+ } else if (this_value->config.isBetterThan(best_value->config,
+ &target_density)) {
+ // Claim the previous value so that it is not included in the base.
+ (*config_claimed_map)[best_value] = true;
+ best_value = this_value;
+ } else {
+ // Claim this value so that it is not included in the base.
+ (*config_claimed_map)[this_value] = true;
+ }
}
+ CHECK(best_value != nullptr);
+ }
}
-bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
- bool error = false;
- for (size_t i = 0; i < mSplitConstraints.size(); i++) {
- for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
- for (const ConfigDescription& config : mSplitConstraints[i].configs) {
- if (mSplitConstraints[j].configs.find(config) !=
- mSplitConstraints[j].configs.end()) {
- context->getDiagnostics()->error(DiagMessage() << "config '" << config
- << "' appears in multiple splits, "
- << "target split ambiguous");
- error = true;
- }
- }
+bool TableSplitter::VerifySplitConstraints(IAaptContext* context) {
+ bool error = false;
+ for (size_t i = 0; i < split_constraints_.size(); i++) {
+ for (size_t j = i + 1; j < split_constraints_.size(); j++) {
+ for (const ConfigDescription& config : split_constraints_[i].configs) {
+ if (split_constraints_[j].configs.find(config) !=
+ split_constraints_[j].configs.end()) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "config '" << config
+ << "' appears in multiple splits, "
+ << "target split ambiguous");
+ error = true;
}
+ }
}
- return !error;
+ }
+ return !error;
}
-void TableSplitter::splitTable(ResourceTable* originalTable) {
- const size_t splitCount = mSplitConstraints.size();
- for (auto& pkg : originalTable->packages) {
- // Initialize all packages for splits.
- for (size_t idx = 0; idx < splitCount; idx++) {
- ResourceTable* splitTable = mSplits[idx].get();
- splitTable->createPackage(pkg->name, pkg->id);
- }
-
- for (auto& type : pkg->types) {
- if (type->type == ResourceType::kMipmap) {
- // Always keep mipmaps.
- continue;
- }
-
- for (auto& entry : type->entries) {
- if (mConfigFilter) {
- // First eliminate any resource that we definitely don't want.
- for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
- if (!mConfigFilter->match(configValue->config)) {
- // null out the entry. We will clean up and remove nulls at the end
- // for performance reasons.
- configValue.reset();
- }
- }
- }
-
- // Organize the values into two separate buckets. Those that are density-dependent
- // and those that are density-independent.
- // One density technically matches all density, it's just that some densities
- // match better. So we need to be aware of the full set of densities to make this
- // decision.
- ConfigDensityGroups densityGroups;
- ConfigClaimedMap configClaimedMap;
- for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
- if (configValue) {
- configClaimedMap[configValue.get()] = false;
-
- if (configValue->config.density != 0) {
- // Create a bucket for this density-dependent config.
- densityGroups[copyWithoutDensity(configValue->config)]
- .push_back(configValue.get());
- }
- }
- }
-
- // First we check all the splits. If it doesn't match one of the splits, we
- // leave it in the base.
- for (size_t idx = 0; idx < splitCount; idx++) {
- const SplitConstraints& splitConstraint = mSplitConstraints[idx];
- ResourceTable* splitTable = mSplits[idx].get();
-
- // Select the values we want from this entry for this split.
- SplitValueSelector selector(splitConstraint);
- std::vector<ResourceConfigValue*> selectedValues =
- selector.selectValues(densityGroups, &configClaimedMap);
-
- // No need to do any work if we selected nothing.
- if (!selectedValues.empty()) {
- // Create the same resource structure in the split. We do this lazily
- // because we might not have actual values for each type/entry.
- ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
- ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
- if (!splitType->id) {
- splitType->id = type->id;
- splitType->symbolStatus = type->symbolStatus;
- }
-
- ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
- if (!splitEntry->id) {
- splitEntry->id = entry->id;
- splitEntry->symbolStatus = entry->symbolStatus;
- }
-
- // Copy the selected values into the new Split Entry.
- for (ResourceConfigValue* configValue : selectedValues) {
- ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
- configValue->config, configValue->product);
- newConfigValue->value = std::unique_ptr<Value>(
- configValue->value->clone(&splitTable->stringPool));
- }
- }
- }
-
- if (mPreferredDensity) {
- markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
- densityGroups,
- &configClaimedMap);
- }
-
- // All splits are handled, now check to see what wasn't claimed and remove
- // whatever exists in other splits.
- for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
- if (configValue && configClaimedMap[configValue.get()]) {
- // Claimed, remove from base.
- configValue.reset();
- }
- }
-
- // Now erase all nullptrs.
- entry->values.erase(
- std::remove(entry->values.begin(), entry->values.end(), nullptr),
- entry->values.end());
- }
- }
+void TableSplitter::SplitTable(ResourceTable* original_table) {
+ const size_t split_count = split_constraints_.size();
+ for (auto& pkg : original_table->packages) {
+ // Initialize all packages for splits.
+ for (size_t idx = 0; idx < split_count; idx++) {
+ ResourceTable* split_table = splits_[idx].get();
+ split_table->CreatePackage(pkg->name, pkg->id);
}
+
+ for (auto& type : pkg->types) {
+ if (type->type == ResourceType::kMipmap) {
+ // Always keep mipmaps.
+ continue;
+ }
+
+ for (auto& entry : type->entries) {
+ if (options_.config_filter) {
+ // First eliminate any resource that we definitely don't want.
+ for (std::unique_ptr<ResourceConfigValue>& config_value :
+ entry->values) {
+ if (!options_.config_filter->Match(config_value->config)) {
+ // null out the entry. We will clean up and remove nulls at the
+ // end for performance reasons.
+ config_value.reset();
+ }
+ }
+ }
+
+ // Organize the values into two separate buckets. Those that are
+ // density-dependent
+ // and those that are density-independent.
+ // One density technically matches all density, it's just that some
+ // densities
+ // match better. So we need to be aware of the full set of densities to
+ // make this
+ // decision.
+ ConfigDensityGroups density_groups;
+ ConfigClaimedMap config_claimed_map;
+ for (const std::unique_ptr<ResourceConfigValue>& config_value :
+ entry->values) {
+ if (config_value) {
+ config_claimed_map[config_value.get()] = false;
+
+ if (config_value->config.density != 0) {
+ // Create a bucket for this density-dependent config.
+ density_groups[CopyWithoutDensity(config_value->config)]
+ .push_back(config_value.get());
+ }
+ }
+ }
+
+ // First we check all the splits. If it doesn't match one of the splits,
+ // we
+ // leave it in the base.
+ for (size_t idx = 0; idx < split_count; idx++) {
+ const SplitConstraints& split_constraint = split_constraints_[idx];
+ ResourceTable* split_table = splits_[idx].get();
+
+ // Select the values we want from this entry for this split.
+ SplitValueSelector selector(split_constraint);
+ std::vector<ResourceConfigValue*> selected_values =
+ selector.SelectValues(density_groups, &config_claimed_map);
+
+ // No need to do any work if we selected nothing.
+ if (!selected_values.empty()) {
+ // Create the same resource structure in the split. We do this
+ // lazily because we might not have actual values for each
+ // type/entry.
+ ResourceTablePackage* split_pkg =
+ split_table->FindPackage(pkg->name);
+ ResourceTableType* split_type =
+ split_pkg->FindOrCreateType(type->type);
+ if (!split_type->id) {
+ split_type->id = type->id;
+ split_type->symbol_status = type->symbol_status;
+ }
+
+ ResourceEntry* split_entry =
+ split_type->FindOrCreateEntry(entry->name);
+ if (!split_entry->id) {
+ split_entry->id = entry->id;
+ split_entry->symbol_status = entry->symbol_status;
+ }
+
+ // Copy the selected values into the new Split Entry.
+ for (ResourceConfigValue* config_value : selected_values) {
+ ResourceConfigValue* new_config_value =
+ split_entry->FindOrCreateValue(config_value->config,
+ config_value->product);
+ new_config_value->value = std::unique_ptr<Value>(
+ config_value->value->Clone(&split_table->string_pool));
+ }
+ }
+ }
+
+ if (options_.preferred_density) {
+ MarkNonPreferredDensitiesAsClaimed(options_.preferred_density.value(),
+ density_groups,
+ &config_claimed_map);
+ }
+
+ // All splits are handled, now check to see what wasn't claimed and
+ // remove
+ // whatever exists in other splits.
+ for (std::unique_ptr<ResourceConfigValue>& config_value :
+ entry->values) {
+ if (config_value && config_claimed_map[config_value.get()]) {
+ // Claimed, remove from base.
+ config_value.reset();
+ }
+ }
+
+ // Now erase all nullptrs.
+ entry->values.erase(
+ std::remove(entry->values.begin(), entry->values.end(), nullptr),
+ entry->values.end());
+ }
+ }
+ }
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h
index 2fa5c47..1ae3271 100644
--- a/tools/aapt2/split/TableSplitter.h
+++ b/tools/aapt2/split/TableSplitter.h
@@ -17,62 +17,58 @@
#ifndef AAPT_SPLIT_TABLESPLITTER_H
#define AAPT_SPLIT_TABLESPLITTER_H
+#include <set>
+#include <vector>
+#include "android-base/macros.h"
+
#include "ConfigDescription.h"
#include "ResourceTable.h"
#include "filter/ConfigFilter.h"
#include "process/IResourceTableConsumer.h"
-#include <android-base/macros.h>
-#include <set>
-#include <vector>
-
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> preferred_density;
- /**
- * 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* config_filter = 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)
+ : split_constraints_(splits), options_(options) {
+ for (size_t i = 0; i < split_constraints_.size(); i++) {
+ splits_.push_back(util::make_unique<ResourceTable>());
}
+ }
- bool verifySplitConstraints(IAaptContext* context);
+ bool VerifySplitConstraints(IAaptContext* context);
- void splitTable(ResourceTable* originalTable);
+ void SplitTable(ResourceTable* original_table);
- std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
- return mSplits;
- }
+ std::vector<std::unique_ptr<ResourceTable>>& splits() { return splits_; }
-private:
- std::vector<SplitConstraints> mSplitConstraints;
- std::vector<std::unique_ptr<ResourceTable>> mSplits;
- Maybe<uint16_t> mPreferredDensity;
- IConfigFilter* mConfigFilter;
+ private:
+ std::vector<SplitConstraints> split_constraints_;
+ std::vector<std::unique_ptr<ResourceTable>> splits_;
+ TableSplitterOptions options_;
- DISALLOW_COPY_AND_ASSIGN(TableSplitter);
+ DISALLOW_COPY_AND_ASSIGN(TableSplitter);
};
-
}
#endif /* AAPT_SPLIT_TABLESPLITTER_H */
diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp
index 5150e82..088dac3 100644
--- a/tools/aapt2/split/TableSplitter_test.cpp
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -15,155 +15,192 @@
*/
#include "split/TableSplitter.h"
+
#include "test/Test.h"
namespace aapt {
TEST(TableSplitterTest, NoSplitPreferredDensity) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addFileReference("android:drawable/icon", "res/drawable-mdpi/icon.png",
- test::parseConfigOrDie("mdpi"))
- .addFileReference("android:drawable/icon", "res/drawable-hdpi/icon.png",
- test::parseConfigOrDie("hdpi"))
- .addFileReference("android:drawable/icon", "res/drawable-xhdpi/icon.png",
- test::parseConfigOrDie("xhdpi"))
- .addFileReference("android:drawable/icon", "res/drawable-xxhdpi/icon.png",
- test::parseConfigOrDie("xxhdpi"))
- .addSimple("android:string/one")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-mdpi/icon.png",
+ test::ParseConfigOrDie("mdpi"))
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-hdpi/icon.png",
+ test::ParseConfigOrDie("hdpi"))
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-xhdpi/icon.png",
+ test::ParseConfigOrDie("xhdpi"))
+ .AddFileReference("android:drawable/icon",
+ "res/drawable-xxhdpi/icon.png",
+ test::ParseConfigOrDie("xxhdpi"))
+ .AddSimple("android:string/one")
+ .Build();
- TableSplitterOptions options;
- options.preferredDensity = ConfigDescription::DENSITY_XHIGH;
- TableSplitter splitter({}, options);
- splitter.splitTable(table.get());
+ TableSplitterOptions options;
+ options.preferred_density = ConfigDescription::DENSITY_XHIGH;
+ TableSplitter splitter({}, options);
+ splitter.SplitTable(table.get());
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
- "android:drawable/icon",
- test::parseConfigOrDie("mdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
- "android:drawable/icon",
- test::parseConfigOrDie("hdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(),
- "android:drawable/icon",
- test::parseConfigOrDie("xhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
- "android:drawable/icon",
- test::parseConfigOrDie("xxhdpi")));
- EXPECT_NE(nullptr, test::getValue<Id>(table.get(), "android:string/one"));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/icon",
+ test::ParseConfigOrDie("xxhdpi")));
+ EXPECT_NE(nullptr, test::GetValue<Id>(table.get(), "android:string/one"));
}
TEST(TableSplitterTest, SplitTableByDensity) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addFileReference("android:drawable/foo", "res/drawable-mdpi/foo.png",
- test::parseConfigOrDie("mdpi"))
- .addFileReference("android:drawable/foo", "res/drawable-hdpi/foo.png",
- test::parseConfigOrDie("hdpi"))
- .addFileReference("android:drawable/foo", "res/drawable-xhdpi/foo.png",
- test::parseConfigOrDie("xhdpi"))
- .addFileReference("android:drawable/foo", "res/drawable-xxhdpi/foo.png",
- test::parseConfigOrDie("xxhdpi"))
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/foo", "res/drawable-mdpi/foo.png",
+ test::ParseConfigOrDie("mdpi"))
+ .AddFileReference("android:drawable/foo", "res/drawable-hdpi/foo.png",
+ test::ParseConfigOrDie("hdpi"))
+ .AddFileReference("android:drawable/foo",
+ "res/drawable-xhdpi/foo.png",
+ test::ParseConfigOrDie("xhdpi"))
+ .AddFileReference("android:drawable/foo",
+ "res/drawable-xxhdpi/foo.png",
+ test::ParseConfigOrDie("xxhdpi"))
+ .Build();
- std::vector<SplitConstraints> constraints;
- constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("mdpi") } });
- constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("hdpi") } });
- constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("xhdpi") } });
+ std::vector<SplitConstraints> constraints;
+ constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("mdpi")}});
+ constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("hdpi")}});
+ constraints.push_back(SplitConstraints{{test::ParseConfigOrDie("xhdpi")}});
- TableSplitter splitter(constraints, TableSplitterOptions{});
- splitter.splitTable(table.get());
+ TableSplitter splitter(constraints, TableSplitterOptions{});
+ splitter.SplitTable(table.get());
- ASSERT_EQ(3u, splitter.getSplits().size());
+ ASSERT_EQ(3u, splitter.splits().size());
- ResourceTable* splitOne = splitter.getSplits()[0].get();
- ResourceTable* splitTwo = splitter.getSplits()[1].get();
- ResourceTable* splitThree = splitter.getSplits()[2].get();
+ ResourceTable* split_one = splitter.splits()[0].get();
+ ResourceTable* split_two = splitter.splits()[1].get();
+ ResourceTable* split_three = splitter.splits()[2].get();
- // Just xxhdpi should be in the base.
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo",
- test::parseConfigOrDie("mdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo",
- test::parseConfigOrDie("hdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo",
- test::parseConfigOrDie("xhdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo",
- test::parseConfigOrDie("xxhdpi")));
+ // Just xxhdpi should be in the base.
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ table.get(), "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
- // Each split should have one and only one drawable.
- EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo",
- test::parseConfigOrDie("mdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo",
- test::parseConfigOrDie("hdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo",
- test::parseConfigOrDie("xhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo",
- test::parseConfigOrDie("xxhdpi")));
+ // Each split should have one and only one drawable.
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_one, "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo",
- test::parseConfigOrDie("mdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo",
- test::parseConfigOrDie("hdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo",
- test::parseConfigOrDie("xhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo",
- test::parseConfigOrDie("xxhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_two, "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo",
- test::parseConfigOrDie("mdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo",
- test::parseConfigOrDie("hdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo",
- test::parseConfigOrDie("xhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo",
- test::parseConfigOrDie("xxhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("mdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("hdpi")));
+ EXPECT_NE(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("xhdpi")));
+ EXPECT_EQ(nullptr, test::GetValueForConfig<FileReference>(
+ split_three, "android:drawable/foo",
+ test::ParseConfigOrDie("xxhdpi")));
}
TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
- ResourceTable table;
+ ResourceTable table;
- const ResourceName foo = test::parseNameOrDie("android:string/foo");
- ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {},
- util::make_unique<Id>(),
- test::getDiagnostics()));
- ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xhdpi"), {},
- util::make_unique<Id>(),
- test::getDiagnostics()));
- ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xxhdpi"), {},
- util::make_unique<Id>(),
- test::getDiagnostics()));
+ const ResourceName foo = test::ParseNameOrDie("android:string/foo");
+ ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-hdpi"), {},
+ util::make_unique<Id>(),
+ test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xhdpi"), {},
+ util::make_unique<Id>(),
+ test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xxhdpi"), {},
+ util::make_unique<Id>(),
+ test::GetDiagnostics()));
- std::vector<SplitConstraints> constraints;
- constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-mdpi") } });
- constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-xhdpi") } });
+ std::vector<SplitConstraints> constraints;
+ constraints.push_back(
+ SplitConstraints{{test::ParseConfigOrDie("land-mdpi")}});
+ constraints.push_back(
+ SplitConstraints{{test::ParseConfigOrDie("land-xhdpi")}});
- TableSplitter splitter(constraints, TableSplitterOptions{});
- splitter.splitTable(&table);
+ TableSplitter splitter(constraints, TableSplitterOptions{});
+ splitter.SplitTable(&table);
- ASSERT_EQ(2u, splitter.getSplits().size());
+ ASSERT_EQ(2u, splitter.splits().size());
- ResourceTable* splitOne = splitter.getSplits()[0].get();
- ResourceTable* splitTwo = splitter.getSplits()[1].get();
+ ResourceTable* split_one = splitter.splits()[0].get();
+ ResourceTable* split_two = splitter.splits()[1].get();
- // All but the xxhdpi resource should be gone, since there were closer matches in land-xhdpi.
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo",
- test::parseConfigOrDie("land-hdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo",
- test::parseConfigOrDie("land-xhdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo",
- test::parseConfigOrDie("land-xxhdpi")));
+ // All but the xxhdpi resource should be gone, since there were closer matches
+ // in land-xhdpi.
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(&table, "android:string/foo",
+ test::ParseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(&table, "android:string/foo",
+ test::ParseConfigOrDie("land-xhdpi")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(&table, "android:string/foo",
+ test::ParseConfigOrDie("land-xxhdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo",
- test::parseConfigOrDie("land-hdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo",
- test::parseConfigOrDie("land-xhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo",
- test::parseConfigOrDie("land-xxhdpi")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(split_one, "android:string/foo",
+ test::ParseConfigOrDie("land-hdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_one, "android:string/foo",
+ test::ParseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_one, "android:string/foo",
+ test::ParseConfigOrDie("land-xxhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, "android:string/foo",
- test::parseConfigOrDie("land-hdpi")));
- EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, "android:string/foo",
- test::parseConfigOrDie("land-xhdpi")));
- EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, "android:string/foo",
- test::parseConfigOrDie("land-xxhdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_two, "android:string/foo",
+ test::ParseConfigOrDie("land-hdpi")));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfig<Id>(split_two, "android:string/foo",
+ test::ParseConfigOrDie("land-xhdpi")));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfig<Id>(split_two, "android:string/foo",
+ test::ParseConfigOrDie("land-xxhdpi")));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 637e991..9377306 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -17,244 +17,269 @@
#ifndef AAPT_TEST_BUILDERS_H
#define AAPT_TEST_BUILDERS_H
+#include <memory>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "test/Common.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
-#include <memory>
-
namespace aapt {
namespace test {
class ResourceTableBuilder {
-private:
- DummyDiagnosticsImpl mDiagnostics;
- std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>();
+ public:
+ ResourceTableBuilder() = default;
-public:
- ResourceTableBuilder() = default;
+ StringPool* string_pool() { return &table_->string_pool; }
- StringPool* getStringPool() {
- return &mTable->stringPool;
- }
+ ResourceTableBuilder& SetPackageId(const StringPiece& package_name,
+ uint8_t id) {
+ ResourceTablePackage* package = table_->CreatePackage(package_name, id);
+ CHECK(package != nullptr);
+ 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>(table_->string_pool.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>(
+ table_->string_pool.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>(
+ table_->string_pool.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>(
+ table_->string_pool.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 res_name = ParseNameOrDie(name);
+ CHECK(table_->AddResourceAllowMangled(res_name, id, config, {},
+ std::move(value), &diagnostics_));
+ 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 res_name = ParseNameOrDie(name);
+ Symbol symbol;
+ symbol.state = state;
+ CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol,
+ &diagnostics_));
+ 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(table_); }
- std::unique_ptr<ResourceTable> build() {
- return std::move(mTable);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder);
+
+ DummyDiagnosticsImpl diagnostics_;
+ std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
};
-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;
+ public:
+ template <typename... Args>
+ explicit ValueBuilder(Args&&... args)
+ : value_(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) {
+ value_->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) {
+ value_->SetComment(str);
+ return *this;
+ }
- ValueBuilder& setComment(const StringPiece& str) {
- mValue->setComment(str);
- return *this;
- }
+ std::unique_ptr<Value> Build() { return std::move(value_); }
- std::unique_ptr<Value> build() {
- return std::move(mValue);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ValueBuilder);
+
+ std::unique_ptr<Value> value_;
};
class AttributeBuilder {
-private:
- std::unique_ptr<Attribute> mAttr;
+ public:
+ explicit AttributeBuilder(bool weak = false)
+ : attr_(util::make_unique<Attribute>(weak)) {
+ attr_->type_mask = 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) {
+ attr_->type_mask = typeMask;
+ return *this;
+ }
- AttributeBuilder& setTypeMask(uint32_t typeMask) {
- mAttr->typeMask = typeMask;
- return *this;
- }
+ AttributeBuilder& AddItem(const StringPiece& name, uint32_t value) {
+ attr_->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(attr_); }
- std::unique_ptr<Attribute> build() {
- return std::move(mAttr);
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AttributeBuilder);
+
+ std::unique_ptr<Attribute> attr_;
};
class StyleBuilder {
-private:
- std::unique_ptr<Style> mStyle = util::make_unique<Style>();
+ public:
+ StyleBuilder() = default;
-public:
- StyleBuilder& setParent(const StringPiece& str) {
- mStyle->parent = Reference(parseNameOrDie(str));
- return *this;
- }
+ StyleBuilder& SetParent(const StringPiece& str) {
+ style_->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) {
+ style_->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));
+ style_->entries.back().key.id = id;
+ return *this;
+ }
- std::unique_ptr<Style> build() {
- return std::move(mStyle);
- }
+ std::unique_ptr<Style> Build() { return std::move(style_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StyleBuilder);
+
+ std::unique_ptr<Style> style_ = util::make_unique<Style>();
};
class StyleableBuilder {
-private:
- std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>();
+ public:
+ StyleableBuilder() = default;
-public:
- StyleableBuilder& addItem(const StringPiece& str, const Maybe<ResourceId>& id = {}) {
- mStyleable->entries.push_back(Reference(parseNameOrDie(str)));
- mStyleable->entries.back().id = id;
- return *this;
- }
+ StyleableBuilder& AddItem(const StringPiece& str,
+ const Maybe<ResourceId>& id = {}) {
+ styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
+ styleable_->entries.back().id = id;
+ return *this;
+ }
- std::unique_ptr<Styleable> build() {
- return std::move(mStyleable);
- }
+ std::unique_ptr<Styleable> Build() { return std::move(styleable_); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StyleableBuilder);
+
+ std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
};
-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;
+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"));
+ CHECK(doc != nullptr) << "failed to parse inline XML string";
+ 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..3689201 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -17,6 +17,12 @@
#ifndef AAPT_TEST_COMMON_H
#define AAPT_TEST_COMMON_H
+#include <iostream>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "gtest/gtest.h"
+
#include "ConfigDescription.h"
#include "Debug.h"
#include "ResourceTable.h"
@@ -26,11 +32,9 @@
#include "process/IResourceTableConsumer.h"
#include "util/StringPiece.h"
-#include <gtest/gtest.h>
-#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 +45,84 @@
namespace test {
struct DummyDiagnosticsImpl : public IDiagnostics {
- void log(Level level, DiagMessageActual& actualMsg) override {
- switch (level) {
- case Level::Note:
- return;
+ void Log(Level level, DiagMessageActual& actual_msg) 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 << actual_msg.source << ": warn: " << actual_msg.message
+ << "." << std::endl;
+ break;
- case Level::Error:
- std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl;
- break;
- }
+ case Level::Error:
+ std::cerr << actual_msg.source << ": error: " << actual_msg.message
+ << "." << std::endl;
+ break;
}
+ }
};
-inline IDiagnostics* getDiagnostics() {
- static DummyDiagnosticsImpl diag;
- return &diag;
+inline IDiagnostics* GetDiagnostics() {
+ 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();
+inline ResourceName ParseNameOrDie(const StringPiece& str) {
+ ResourceNameRef ref;
+ CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "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;
+inline ConfigDescription ParseConfigOrDie(const StringPiece& str) {
+ ConfigDescription config;
+ CHECK(ConfigDescription::Parse(str, &config)) << "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& res_name,
+ const ConfigDescription& config,
+ const StringPiece& product) {
+ Maybe<ResourceTable::SearchResult> result =
+ table->FindResource(ParseNameOrDie(res_name));
+ if (result) {
+ ResourceConfigValue* config_value =
+ result.value().entry->FindValue(config, product);
+ if (config_value) {
+ return ValueCast<T>(config_value->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& res_name,
+ const ConfigDescription& config) {
+ return GetValueForConfigAndProduct<T>(table, res_name, 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& res_name) {
+ return GetValueForConfig<T>(table, res_name, {});
}
class TestFile : public io::IFile {
-private:
- Source mSource;
+ public:
+ explicit TestFile(const StringPiece& path) : source_(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 source_; }
- const Source& getSource() const override {
- return mSource;
- }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestFile);
+
+ Source source_;
};
-} // 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..7986329 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -17,165 +17,162 @@
#ifndef AAPT_TEST_CONTEXT_H
#define AAPT_TEST_CONTEXT_H
+#include <list>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
#include "NameMangler.h"
-#include "util/Util.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "test/Common.h"
-
-#include <cassert>
-#include <list>
+#include "util/Util.h"
namespace aapt {
namespace test {
class Context : public IAaptContext {
-public:
- SymbolTable* getExternalSymbols() override {
- return &mSymbols;
- }
+ public:
+ Context() = default;
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ SymbolTable* GetExternalSymbols() override { return &symbols_; }
- const std::string& getCompilationPackage() override {
- assert(mCompilationPackage && "package name not set");
- return mCompilationPackage.value();
- }
+ IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
- uint8_t getPackageId() override {
- assert(mPackageId && "package ID not set");
- return mPackageId.value();
- }
+ const std::string& GetCompilationPackage() override {
+ CHECK(bool(compilation_package_)) << "package name not set";
+ return compilation_package_.value();
+ }
- NameMangler* getNameMangler() override {
- return &mNameMangler;
- }
+ uint8_t GetPackageId() override {
+ CHECK(bool(package_id_)) << "package ID not set";
+ return package_id_.value();
+ }
- bool verbose() override {
- return false;
- }
+ NameMangler* GetNameMangler() override { return &name_mangler_; }
- int getMinSdkVersion() override {
- return mMinSdkVersion;
- }
+ bool IsVerbose() override { return false; }
-private:
- friend class ContextBuilder;
+ int GetMinSdkVersion() override { return min_sdk_version_; }
- Maybe<std::string> mCompilationPackage;
- Maybe<uint8_t> mPackageId;
- StdErrDiagnostics mDiagnostics;
- SymbolTable mSymbols;
- NameMangler mNameMangler = NameMangler({});
- int mMinSdkVersion = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Context);
+
+ friend class ContextBuilder;
+
+ Maybe<std::string> compilation_package_;
+ Maybe<uint8_t> package_id_;
+ StdErrDiagnostics diagnostics_;
+ SymbolTable symbols_;
+ NameMangler name_mangler_ = NameMangler({});
+ int min_sdk_version_ = 0;
};
class ContextBuilder {
-private:
- std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context());
+ public:
+ ContextBuilder& SetCompilationPackage(const StringPiece& package) {
+ context_->compilation_package_ = package.ToString();
+ return *this;
+ }
-public:
- ContextBuilder& setCompilationPackage(const StringPiece& package) {
- mContext->mCompilationPackage = package.toString();
- return *this;
- }
+ ContextBuilder& SetPackageId(uint8_t id) {
+ context_->package_id_ = id;
+ return *this;
+ }
- ContextBuilder& setPackageId(uint8_t id) {
- mContext->mPackageId = id;
- return *this;
- }
+ ContextBuilder& SetNameManglerPolicy(const NameManglerPolicy& policy) {
+ context_->name_mangler_ = NameMangler(policy);
+ return *this;
+ }
- ContextBuilder& setNameManglerPolicy(const NameManglerPolicy& policy) {
- mContext->mNameMangler = NameMangler(policy);
- return *this;
- }
+ ContextBuilder& AddSymbolSource(std::unique_ptr<ISymbolSource> src) {
+ context_->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 min_sdk) {
+ context_->min_sdk_version_ = min_sdk;
+ return *this;
+ }
- ContextBuilder& setMinSdkVersion(int minSdk) {
- mContext->mMinSdkVersion = minSdk;
- return *this;
- }
+ std::unique_ptr<Context> Build() { return std::move(context_); }
- std::unique_ptr<Context> build() {
- return std::move(mContext);
- }
+ private:
+ std::unique_ptr<Context> context_ = std::unique_ptr<Context>(new Context());
};
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);
+ symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get();
+ symbol_source_->id_map_[id] = symbol.get();
+ symbol_source_->symbols_.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);
+ symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get();
+ symbol_source_->id_map_[id] = symbol.get();
+ symbol_source_->symbols_.push_back(std::move(symbol));
+ return *this;
+ }
+
+ std::unique_ptr<ISymbolSource> Build() { return std::move(symbol_source_); }
+
+ private:
+ class StaticSymbolSource : public ISymbolSource {
+ public:
+ StaticSymbolSource() = default;
+
+ std::unique_ptr<SymbolTable::Symbol> FindByName(
+ const ResourceName& name) override {
+ auto iter = name_map_.find(name);
+ if (iter != name_map_.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 = id_map_.find(id);
+ if (iter != id_map_.end()) {
+ return CloneSymbol(iter->second);
+ }
+ return nullptr;
}
- std::unique_ptr<ISymbolSource> build() {
- return std::move(mSymbolSource);
+ std::list<std::unique_ptr<SymbolTable::Symbol>> symbols_;
+ std::map<ResourceName, SymbolTable::Symbol*> name_map_;
+ std::map<ResourceId, SymbolTable::Symbol*> id_map_;
+
+ 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->is_public = sym->is_public;
+ 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> symbol_source_ =
+ 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..ec07432 100644
--- a/tools/aapt2/test/Test.h
+++ b/tools/aapt2/test/Test.h
@@ -17,16 +17,10 @@
#ifndef AAPT_TEST_TEST_H
#define AAPT_TEST_TEST_H
+#include "gtest/gtest.h"
+
#include "test/Builders.h"
#include "test/Common.h"
#include "test/Context.h"
-#include <gtest/gtest.h>
-
-namespace aapt {
-namespace test {
-
-} // 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..aeabcff 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -14,22 +14,25 @@
* limitations under the License.
*/
+#include "unflatten/BinaryResourceParser.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/TypeWrappers.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 <androidfw/ResourceTypes.h>
-#include <androidfw/TypeWrappers.h>
-#include <android-base/macros.h>
-#include <algorithm>
-#include <map>
-#include <string>
-
namespace aapt {
using namespace android;
@@ -41,533 +44,552 @@
* given a mapping from resource ID to resource name.
*/
class ReferenceIdToNameVisitor : public ValueVisitor {
-private:
- const std::map<ResourceId, ResourceName>* mMapping;
+ public:
+ using ValueVisitor::Visit;
-public:
- using ValueVisitor::visit;
+ explicit ReferenceIdToNameVisitor(
+ const std::map<ResourceId, ResourceName>* mapping)
+ : mapping_(mapping) {
+ CHECK(mapping_ != nullptr);
+ }
- explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
- mMapping(mapping) {
- assert(mMapping);
+ void Visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().is_valid()) {
+ 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 cache_iter = mapping_->find(id);
+ if (cache_iter != mapping_->end()) {
+ reference->name = cache_iter->second;
}
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor);
+
+ const std::map<ResourceId, ResourceName>* mapping_;
};
-} // 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)
+ : context_(context),
+ table_(table),
+ source_(source),
+ data_(data),
+ data_len_(len) {}
-bool BinaryResourceParser::parse() {
- ResChunkPullParser parser(mData, mDataLen);
+bool BinaryResourceParser::Parse() {
+ ResChunkPullParser parser(data_, data_len_);
- 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.chunk()->type != android::RES_TABLE_TYPE) {
+ context_->GetDiagnostics()->Warn(DiagMessage(source_)
+ << "unknown chunk of type '"
+ << (int)parser.chunk()->type << "'");
+ continue;
}
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt resource table: "
- << parser.getLastError());
- return false;
+ if (!ParseTable(parser.chunk())) {
+ error = true;
}
- return !error;
+ }
+
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt resource table: " << parser.error());
+ 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;
- }
+bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
+ const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
+ if (!table_header) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "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(&table_header->header),
+ GetChunkDataLen(&table_header->header));
+ while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+ switch (util::DeviceToHost16(parser.chunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (value_pool_.getError() == NO_INIT) {
+ status_t err = value_pool_.setTo(
+ parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+ if (err != NO_ERROR) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt string pool in ResTable: "
+ << value_pool_.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.
+ table_->string_pool.HintWillAdd(value_pool_.size(),
+ value_pool_.styleCount());
+ } else {
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(source_) << "unexpected string pool in ResTable");
}
- }
+ break;
- 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;
- }
-
- 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]);
- }
-
- 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();
-
- 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;
+ case android::RES_TABLE_PACKAGE_TYPE:
+ if (!ParsePackage(parser.chunk())) {
+ return false;
}
- }
+ break;
- if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt ResTable_package: "
- << parser.getLastError());
- return false;
+ default:
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(source_)
+ << "unexpected chunk type "
+ << (int)util::DeviceToHost16(parser.chunk()->type));
+ break;
}
+ }
- // Now go through the table and change local resource ID references to
- // symbolic references.
- ReferenceIdToNameVisitor visitor(&mIdIndex);
- visitAllValuesInTable(mTable, &visitor);
- return true;
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt resource table: " << parser.error());
+ return false;
+ }
+ 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;
- }
+bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
+ const ResTable_package* package_header = ConvertTo<ResTable_package>(chunk);
+ if (!package_header) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_package chunk");
+ return false;
+ }
- const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
- if (!typeSpec) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt ResTable_typeSpec chunk");
- return false;
- }
+ uint32_t package_id = util::DeviceToHost32(package_header->id);
+ if (package_id > std::numeric_limits<uint8_t>::max()) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "package ID is too big (" << package_id << ")");
+ return false;
+ }
- if (typeSpec->id == 0) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "ResTable_typeSpec has invalid id: " << typeSpec->id);
- return false;
+ // Extract the package name.
+ size_t len = strnlen16((const char16_t*)package_header->name,
+ arraysize(package_header->name));
+ std::u16string package_name;
+ package_name.resize(len);
+ for (size_t i = 0; i < len; i++) {
+ package_name[i] = util::DeviceToHost16(package_header->name[i]);
+ }
+
+ ResourceTablePackage* package = table_->CreatePackage(
+ util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
+ if (!package) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "incompatible package '" << package_name
+ << "' with ID " << package_id);
+ 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.
+ type_pool_.uninit();
+ key_pool_.uninit();
+
+ ResChunkPullParser parser(GetChunkData(&package_header->header),
+ GetChunkDataLen(&package_header->header));
+ while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+ switch (util::DeviceToHost16(parser.chunk()->type)) {
+ case android::RES_STRING_POOL_TYPE:
+ if (type_pool_.getError() == NO_INIT) {
+ status_t err = type_pool_.setTo(
+ parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+ if (err != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt type string pool in "
+ << "ResTable_package: "
+ << type_pool_.getError());
+ return false;
+ }
+ } else if (key_pool_.getError() == NO_INIT) {
+ status_t err = key_pool_.setTo(
+ parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+ if (err != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt key string pool in "
+ << "ResTable_package: "
+ << key_pool_.getError());
+ return false;
+ }
+ } else {
+ context_->GetDiagnostics()->Warn(DiagMessage(source_)
+ << "unexpected string pool");
+ }
+ break;
+
+ case android::RES_TABLE_TYPE_SPEC_TYPE:
+ if (!ParseTypeSpec(parser.chunk())) {
+ return false;
+ }
+ break;
+
+ case android::RES_TABLE_TYPE_TYPE:
+ if (!ParseType(package, parser.chunk())) {
+ return false;
+ }
+ break;
+
+ default:
+ context_->GetDiagnostics()->Warn(
+ DiagMessage(source_)
+ << "unexpected chunk type "
+ << (int)util::DeviceToHost16(parser.chunk()->type));
+ break;
}
- return true;
+ }
+
+ if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
+ return false;
+ }
+
+ // Now go through the table and change local resource ID references to
+ // symbolic references.
+ ReferenceIdToNameVisitor visitor(&id_index_);
+ VisitAllValuesInTable(table_, &visitor);
+ return true;
}
-bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
+bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+ if (type_pool_.getError() != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "missing type string pool");
+ return false;
+ }
+
+ const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
+ if (!type_spec) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_typeSpec chunk");
+ return false;
+ }
+
+ if (type_spec->id == 0) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "ResTable_typeSpec has invalid id: "
+ << type_spec->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 (type_pool_.getError() != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "missing type string pool");
+ return false;
+ }
+
+ if (key_pool_.getError() != NO_ERROR) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "missing key string pool");
+ return false;
+ }
+
+ const ResTable_type* type = ConvertTo<ResTable_type>(chunk);
+ if (!type) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "corrupt ResTable_type chunk");
+ return false;
+ }
+
+ if (type->id == 0) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "ResTable_type has invalid id: "
+ << (int)type->id);
+ return false;
+ }
+
+ ConfigDescription config;
+ config.copyFromDtoH(type->config);
+
+ const std::string type_str = util::GetString(type_pool_, type->id - 1);
+
+ const ResourceType* parsed_type = ParseResourceType(type_str);
+ if (!parsed_type) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "invalid type name '" << type_str
+ << "' 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, *parsed_type,
+ util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)));
+
+ const ResourceId res_id(package->id.value(), type->id,
+ static_cast<uint16_t>(it.index()));
+
+ std::unique_ptr<Value> resource_value;
+ 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.
+ resource_value = ParseMapEntry(name, config, mapEntry);
+ } else {
+ const Res_value* value =
+ (const Res_value*)((const uint8_t*)entry +
+ util::DeviceToHost32(entry->size));
+ resource_value = ParseValue(name, config, value, entry->flags);
+ }
+
+ if (!resource_value) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage(source_) << "failed to parse value for resource " << name
+ << " (" << res_id << ") with configuration '"
+ << config << "'");
+ return false;
+ }
+
+ if (!table_->AddResourceAllowMangled(name, config, {},
+ std::move(resource_value),
+ context_->GetDiagnostics())) {
+ return false;
+ }
+
+ if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+ Symbol symbol;
+ symbol.state = SymbolState::kPublic;
+ symbol.source = source_.WithLine(0);
+ if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol,
+ context_->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 cache_iter = id_index_.find(res_id);
+ if (cache_iter == id_index_.end()) {
+ id_index_.insert({res_id, 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(value_pool_, data);
+
+ const ResStringPool_span* spans = value_pool_.styleAt(data);
+
+ // Check if the string has a valid style associated with it.
+ if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
+ StyleString style_str = {str};
+ while (spans->name.index != ResStringPool_span::END) {
+ style_str.spans.push_back(
+ Span{util::GetString(value_pool_, spans->name.index),
+ spans->firstChar, spans->lastChar});
+ spans++;
+ }
+ return util::make_unique<StyledString>(table_->string_pool.MakeRef(
+ style_str,
+ StringPool::Context(StringPool::Context::kStylePriority, config)));
+ } else {
+ if (name.type != ResourceType::kString && util::StartsWith(str, "res/")) {
+ // This must be a FileReference.
+ return util::make_unique<FileReference>(table_->string_pool.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>(
+ table_->string_pool.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 null_type = {};
+ null_type.dataType = Res_value::TYPE_REFERENCE;
+ return util::make_unique<BinaryPrimitive>(null_type);
}
- 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:
+ LOG(FATAL) << "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& map_entry : map) {
+ if (Res_INTERNALID(util::DeviceToHost32(map_entry.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 style_entry;
+ style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident));
+ style_entry.value = ParseValue(name, config, &map_entry.value, 0);
+ if (!style_entry.value) {
+ return {};
}
- return style;
+ style->entries.push_back(std::move(style_entry));
+ }
+ 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 is_weak =
+ (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
- // 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 type_mask_iter =
+ 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 (type_mask_iter != end(map)) {
+ attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data);
+ }
+
+ for (const ResTable_map& map_entry : map) {
+ if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
+ switch (util::DeviceToHost32(map_entry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->min_int = static_cast<int32_t>(map_entry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->max_int = static_cast<int32_t>(map_entry.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->type_mask &
+ (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+ Attribute::Symbol symbol;
+ symbol.value = util::DeviceToHost32(map_entry.value.data);
+ symbol.symbol = Reference(util::DeviceToHost32(map_entry.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& map_entry : map) {
+ array->items.push_back(ParseValue(name, config, &map_entry.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& map_entry : map) {
+ std::unique_ptr<Item> item = ParseValue(name, config, &map_entry.value, 0);
+ if (!item) {
+ return {};
}
- return plural;
+
+ switch (util::DeviceToHost32(map_entry.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..dc668fd 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -17,16 +17,17 @@
#ifndef AAPT_BINARY_RESOURCE_PARSER_H
#define AAPT_BINARY_RESOURCE_PARSER_H
+#include <string>
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Source.h"
-
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
-#include <string>
-
namespace aapt {
struct SymbolTable_entry;
@@ -39,80 +40,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 data_len);
- 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:
+ DISALLOW_COPY_AND_ASSIGN(BinaryResourceParser);
-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);
+ 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& map_entry, Value* value);
- std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
- const android::ResTable_map_entry* map);
+ IAaptContext* context_;
+ ResourceTable* table_;
- std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config,
- const android::ResTable_map_entry* map);
+ const Source source_;
- /**
- * 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* data_;
+ const size_t data_len_;
- IAaptContext* mContext;
- ResourceTable* mTable;
+ // The standard value string pool for resource values.
+ android::ResStringPool value_pool_;
- const Source mSource;
+ // The string pool that holds the names of the types defined
+ // in this table.
+ android::ResStringPool type_pool_;
- const void* mData;
- const size_t mDataLen;
+ // The string pool that holds the names of the entries defined
+ // in this table.
+ android::ResStringPool key_pool_;
- // 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> id_index_;
};
-} // namespace aapt
+} // namespace aapt
namespace android {
@@ -121,13 +128,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.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp
index 6f8bb1b..5d71ff3 100644
--- a/tools/aapt2/unflatten/ResChunkPullParser.cpp
+++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp
@@ -15,55 +15,60 @@
*/
#include "unflatten/ResChunkPullParser.h"
-#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
#include <cstddef>
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/Util.h"
+
namespace aapt {
using android::ResChunk_header;
-ResChunkPullParser::Event ResChunkPullParser::next() {
- if (!isGoodEvent(mEvent)) {
- return mEvent;
- }
+ResChunkPullParser::Event ResChunkPullParser::Next() {
+ if (!IsGoodEvent(event_)) {
+ return event_;
+ }
- if (mEvent == Event::StartDocument) {
- mCurrentChunk = mData;
- } else {
- mCurrentChunk = (const ResChunk_header*)
- (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size));
- }
+ if (event_ == Event::kStartDocument) {
+ current_chunk_ = data_;
+ } else {
+ current_chunk_ =
+ (const ResChunk_header*)(((const char*)current_chunk_) +
+ util::DeviceToHost32(current_chunk_->size));
+ }
- const std::ptrdiff_t diff = (const char*) mCurrentChunk - (const char*) mData;
- assert(diff >= 0 && "diff is negative");
- const size_t offset = static_cast<const size_t>(diff);
+ const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_;
+ CHECK(diff >= 0) << "diff is negative";
+ const size_t offset = static_cast<const size_t>(diff);
- if (offset == mLen) {
- mCurrentChunk = nullptr;
- return (mEvent = Event::EndDocument);
- } else if (offset + sizeof(ResChunk_header) > mLen) {
- mLastError = "chunk is past the end of the document";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- }
+ if (offset == len_) {
+ current_chunk_ = nullptr;
+ return (event_ = Event::kEndDocument);
+ } else if (offset + sizeof(ResChunk_header) > len_) {
+ error_ = "chunk is past the end of the document";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ }
- if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) {
- mLastError = "chunk has too small header";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- } else if (util::deviceToHost32(mCurrentChunk->size) <
- util::deviceToHost16(mCurrentChunk->headerSize)) {
- mLastError = "chunk's total size is smaller than header";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- } else if (offset + util::deviceToHost32(mCurrentChunk->size) > mLen) {
- mLastError = "chunk's data extends past the end of the document";
- mCurrentChunk = nullptr;
- return (mEvent = Event::BadDocument);
- }
- return (mEvent = Event::Chunk);
+ if (util::DeviceToHost16(current_chunk_->headerSize) <
+ sizeof(ResChunk_header)) {
+ error_ = "chunk has too small header";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ } else if (util::DeviceToHost32(current_chunk_->size) <
+ util::DeviceToHost16(current_chunk_->headerSize)) {
+ error_ = "chunk's total size is smaller than header";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ } else if (offset + util::DeviceToHost32(current_chunk_->size) > len_) {
+ error_ = "chunk's data extends past the end of the document";
+ current_chunk_ = nullptr;
+ return (event_ = Event::kBadDocument);
+ }
+ return (event_ = Event::kChunk);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
index a51d5bf..437fc5c 100644
--- a/tools/aapt2/unflatten/ResChunkPullParser.h
+++ b/tools/aapt2/unflatten/ResChunkPullParser.h
@@ -17,11 +17,13 @@
#ifndef AAPT_RES_CHUNK_PULL_PARSER_H
#define AAPT_RES_CHUNK_PULL_PARSER_H
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
#include <string>
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/Util.h"
+
namespace aapt {
/**
@@ -37,88 +39,88 @@
* pointing to the data portion of a chunk.
*/
class ResChunkPullParser {
-public:
- enum class Event {
- StartDocument,
- EndDocument,
- BadDocument,
+ public:
+ enum class Event {
+ kStartDocument,
+ kEndDocument,
+ kBadDocument,
- Chunk,
- };
+ kChunk,
+ };
- /**
- * 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;
+ Event event() const;
+ const std::string& error() const;
+ const android::ResChunk_header* chunk() 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:
+ DISALLOW_COPY_AND_ASSIGN(ResChunkPullParser);
-private:
- Event mEvent;
- const android::ResChunk_header* mData;
- size_t mLen;
- const android::ResChunk_header* mCurrentChunk;
- std::string mLastError;
+ Event event_;
+ const android::ResChunk_header* data_;
+ size_t len_;
+ const android::ResChunk_header* current_chunk_;
+ std::string error_;
};
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);
+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);
}
-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);
+inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) {
+ return util::DeviceToHost32(chunk->size) -
+ util::DeviceToHost16(chunk->headerSize);
}
//
// Implementation
//
-inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) {
- return event != Event::EndDocument && event != Event::BadDocument;
+inline bool ResChunkPullParser::IsGoodEvent(ResChunkPullParser::Event event) {
+ return event != Event::kEndDocument && event != Event::kBadDocument;
}
-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)
+ : event_(Event::kStartDocument),
+ data_(reinterpret_cast<const android::ResChunk_header*>(data)),
+ len_(len),
+ current_chunk_(nullptr) {}
+
+inline ResChunkPullParser::Event ResChunkPullParser::event() const {
+ return event_;
}
-inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const {
- return mEvent;
+inline const std::string& ResChunkPullParser::error() const { return error_; }
+
+inline const android::ResChunk_header* ResChunkPullParser::chunk() const {
+ return current_chunk_;
}
-inline const std::string& ResChunkPullParser::getLastError() const {
- return mLastError;
-}
+} // namespace aapt
-inline const android::ResChunk_header* ResChunkPullParser::getChunk() const {
- return mCurrentChunk;
-}
-
-} // namespace aapt
-
-#endif // AAPT_RES_CHUNK_PULL_PARSER_H
+#endif // AAPT_RES_CHUNK_PULL_PARSER_H
diff --git a/tools/aapt2/util/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp
index c88e3c1..ef99dca 100644
--- a/tools/aapt2/util/BigBuffer.cpp
+++ b/tools/aapt2/util/BigBuffer.cpp
@@ -20,33 +20,60 @@
#include <memory>
#include <vector>
+#include "android-base/logging.h"
+
namespace aapt {
-void* BigBuffer::nextBlockImpl(size_t size) {
- if (!mBlocks.empty()) {
- Block& block = mBlocks.back();
- if (block.mBlockSize - block.size >= size) {
- void* outBuffer = block.buffer.get() + block.size;
- block.size += size;
- mSize += size;
- return outBuffer;
- }
+void* BigBuffer::NextBlockImpl(size_t size) {
+ if (!blocks_.empty()) {
+ Block& block = blocks_.back();
+ if (block.block_size_ - block.size >= size) {
+ void* out_buffer = block.buffer.get() + block.size;
+ block.size += size;
+ size_ += size;
+ return out_buffer;
}
+ }
- const size_t actualSize = std::max(mBlockSize, size);
+ const size_t actual_size = std::max(block_size_, size);
- Block block = {};
+ Block block = {};
- // Zero-allocate the block's buffer.
- block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actualSize]());
- assert(block.buffer);
+ // Zero-allocate the block's buffer.
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actual_size]());
+ CHECK(block.buffer);
- block.size = size;
- block.mBlockSize = actualSize;
+ block.size = size;
+ block.block_size_ = actual_size;
- mBlocks.push_back(std::move(block));
- mSize += size;
- return mBlocks.back().buffer.get();
+ blocks_.push_back(std::move(block));
+ size_ += size;
+ return blocks_.back().buffer.get();
}
-} // namespace aapt
+void* BigBuffer::NextBlock(size_t* out_size) {
+ if (!blocks_.empty()) {
+ Block& block = blocks_.back();
+ if (block.size != block.block_size_) {
+ void* out_buffer = block.buffer.get() + block.size;
+ size_t size = block.block_size_ - block.size;
+ block.size = block.block_size_;
+ size_ += size;
+ *out_size = size;
+ return out_buffer;
+ }
+ }
+
+ // Zero-allocate the block's buffer.
+ Block block = {};
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[block_size_]());
+ CHECK(block.buffer);
+ block.size = block_size_;
+ block.block_size_ = block_size_;
+ blocks_.push_back(std::move(block));
+ size_ += block_size_;
+ *out_size = block_size_;
+ return blocks_.back().buffer.get();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
index ba8532f..d23c41d 100644
--- a/tools/aapt2/util/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -17,12 +17,14 @@
#ifndef AAPT_BIG_BUFFER_H
#define AAPT_BIG_BUFFER_H
-#include <cassert>
#include <cstring>
#include <memory>
#include <type_traits>
#include <vector>
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
namespace aapt {
/**
@@ -32,130 +34,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;
+ size_t block_size_;
+ };
- /**
- * 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);
+ typedef std::vector<Block>::const_iterator const_iterator;
- /**
- * Moves the specified BigBuffer into this one. When this method
- * returns, buffer is empty.
- */
- void appendBuffer(BigBuffer&& buffer);
+ /**
+ * Create a BigBuffer with block allocation sizes
+ * of block_size.
+ */
+ explicit BigBuffer(size_t block_size);
- /**
- * Pads the block with 'bytes' bytes of zero values.
- */
- void pad(size_t bytes);
+ BigBuffer(BigBuffer&& rhs);
- /**
- * Pads the block so that it aligns on a 4 byte boundary.
- */
- void align4();
+ /**
+ * Number of occupied bytes in all the allocated blocks.
+ */
+ size_t size() const;
- const_iterator begin() const;
- const_iterator end() 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);
-private:
- /**
- * Returns a pointer to a buffer of the requested size.
- * The buffer is zero-initialized.
- */
- void* nextBlockImpl(size_t size);
+ /**
+ * Returns the next block available and puts the size in out_count.
+ * 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* out_count);
- size_t mBlockSize;
- size_t mSize;
- std::vector<Block> mBlocks;
+ /**
+ * 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 block_size() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBuffer);
+
+ /**
+ * Returns a pointer to a buffer of the requested size.
+ * The buffer is zero-initialized.
+ */
+ void* NextBlockImpl(size_t size);
+
+ size_t block_size_;
+ size_t size_;
+ std::vector<Block> blocks_;
};
-inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) {
-}
+inline BigBuffer::BigBuffer(size_t block_size)
+ : block_size_(block_size), size_(0) {}
-inline BigBuffer::BigBuffer(BigBuffer&& rhs) :
- mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) {
-}
+inline BigBuffer::BigBuffer(BigBuffer&& rhs)
+ : block_size_(rhs.block_size_),
+ size_(rhs.size_),
+ blocks_(std::move(rhs.blocks_)) {}
-inline size_t BigBuffer::size() const {
- return mSize;
-}
+inline size_t BigBuffer::size() const { return size_; }
+
+inline size_t BigBuffer::block_size() const { return block_size_; }
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));
+inline T* BigBuffer::NextBlock(size_t count) {
+ static_assert(std::is_standard_layout<T>::value,
+ "T must be standard_layout type");
+ CHECK(count != 0);
+ return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * 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;
+inline void BigBuffer::BackUp(size_t count) {
+ Block& block = blocks_.back();
+ block.size -= count;
+ size_ -= count;
}
-inline void BigBuffer::pad(size_t bytes) {
- nextBlock<char>(bytes);
+inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) {
+ std::move(buffer.blocks_.begin(), buffer.blocks_.end(),
+ std::back_inserter(blocks_));
+ size_ += buffer.size_;
+ buffer.blocks_.clear();
+ buffer.size_ = 0;
}
-inline void BigBuffer::align4() {
- const size_t unaligned = mSize % 4;
- if (unaligned != 0) {
- pad(4 - unaligned);
- }
+inline void BigBuffer::Pad(size_t bytes) { NextBlock<char>(bytes); }
+
+inline void BigBuffer::Align4() {
+ const size_t unaligned = size_ % 4;
+ if (unaligned != 0) {
+ Pad(4 - unaligned);
+ }
}
inline BigBuffer::const_iterator BigBuffer::begin() const {
- return mBlocks.begin();
+ return blocks_.begin();
}
inline BigBuffer::const_iterator BigBuffer::end() const {
- return mBlocks.end();
+ return blocks_.end();
}
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_BIG_BUFFER_H
+#endif // AAPT_BIG_BUFFER_H
diff --git a/tools/aapt2/util/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp
index 2a24f12..12c0b3e 100644
--- a/tools/aapt2/util/BigBuffer_test.cpp
+++ b/tools/aapt2/util/BigBuffer_test.cpp
@@ -16,83 +16,83 @@
#include "util/BigBuffer.h"
-#include <gtest/gtest.h>
+#include "test/Test.h"
namespace aapt {
TEST(BigBufferTest, AllocateSingleBlock) {
- BigBuffer buffer(4);
+ BigBuffer buffer(4);
- EXPECT_NE(nullptr, buffer.nextBlock<char>(2));
- EXPECT_EQ(2u, buffer.size());
+ EXPECT_NE(nullptr, buffer.NextBlock<char>(2));
+ EXPECT_EQ(2u, buffer.size());
}
TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- char* b1 = buffer.nextBlock<char>(8);
- EXPECT_NE(nullptr, b1);
+ char* b1 = buffer.NextBlock<char>(8);
+ EXPECT_NE(nullptr, b1);
- char* b2 = buffer.nextBlock<char>(4);
- EXPECT_NE(nullptr, b2);
+ char* b2 = buffer.NextBlock<char>(4);
+ EXPECT_NE(nullptr, b2);
- EXPECT_EQ(b1 + 8, b2);
+ EXPECT_EQ(b1 + 8, b2);
}
TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- EXPECT_NE(nullptr, buffer.nextBlock<char>(32));
- EXPECT_EQ(32u, buffer.size());
+ EXPECT_NE(nullptr, buffer.NextBlock<char>(32));
+ EXPECT_EQ(32u, buffer.size());
}
TEST(BigBufferTest, AppendAndMoveBlock) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- uint32_t* b1 = buffer.nextBlock<uint32_t>();
+ uint32_t* b1 = buffer.NextBlock<uint32_t>();
+ ASSERT_NE(nullptr, b1);
+ *b1 = 33;
+
+ {
+ BigBuffer buffer2(16);
+ b1 = buffer2.NextBlock<uint32_t>();
ASSERT_NE(nullptr, b1);
- *b1 = 33;
+ *b1 = 44;
- {
- BigBuffer buffer2(16);
- b1 = buffer2.nextBlock<uint32_t>();
- ASSERT_NE(nullptr, b1);
- *b1 = 44;
+ buffer.AppendBuffer(std::move(buffer2));
+ EXPECT_EQ(0u, buffer2.size());
+ EXPECT_EQ(buffer2.begin(), buffer2.end());
+ }
- buffer.appendBuffer(std::move(buffer2));
- EXPECT_EQ(0u, buffer2.size());
- EXPECT_EQ(buffer2.begin(), buffer2.end());
- }
+ EXPECT_EQ(2 * sizeof(uint32_t), buffer.size());
- EXPECT_EQ(2 * sizeof(uint32_t), buffer.size());
+ auto b = buffer.begin();
+ ASSERT_NE(b, buffer.end());
+ ASSERT_EQ(sizeof(uint32_t), b->size);
+ ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+ ++b;
- auto b = buffer.begin();
- ASSERT_NE(b, buffer.end());
- ASSERT_EQ(sizeof(uint32_t), b->size);
- ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
- ++b;
+ ASSERT_NE(b, buffer.end());
+ ASSERT_EQ(sizeof(uint32_t), b->size);
+ ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+ ++b;
- ASSERT_NE(b, buffer.end());
- ASSERT_EQ(sizeof(uint32_t), b->size);
- ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
- ++b;
-
- ASSERT_EQ(b, buffer.end());
+ ASSERT_EQ(b, buffer.end());
}
TEST(BigBufferTest, PadAndAlignProperly) {
- BigBuffer buffer(16);
+ BigBuffer buffer(16);
- ASSERT_NE(buffer.nextBlock<char>(2), nullptr);
- ASSERT_EQ(2u, buffer.size());
- buffer.pad(2);
- ASSERT_EQ(4u, buffer.size());
- buffer.align4();
- ASSERT_EQ(4u, buffer.size());
- buffer.pad(2);
- ASSERT_EQ(6u, buffer.size());
- buffer.align4();
- ASSERT_EQ(8u, buffer.size());
+ ASSERT_NE(buffer.NextBlock<char>(2), nullptr);
+ ASSERT_EQ(2u, buffer.size());
+ buffer.Pad(2);
+ ASSERT_EQ(4u, buffer.size());
+ buffer.Align4();
+ ASSERT_EQ(4u, buffer.size());
+ buffer.Pad(2);
+ ASSERT_EQ(6u, buffer.size());
+ buffer.Align4();
+ ASSERT_EQ(8u, buffer.size());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index c9b3811..f034607 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -15,15 +15,20 @@
*/
#include "util/Files.h"
-#include "util/Util.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
#include <algorithm>
-#include <android-base/file.h>
#include <cerrno>
#include <cstdio>
-#include <dirent.h>
#include <string>
-#include <sys/stat.h>
+
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/logging.h"
+
+#include "util/Util.h"
#ifdef _WIN32
// Windows includes.
@@ -33,244 +38,230 @@
namespace aapt {
namespace file {
-FileType getFileType(const StringPiece& path) {
- struct stat sb;
- if (stat(path.data(), &sb) < 0) {
- if (errno == ENOENT || errno == ENOTDIR) {
- return FileType::kNonexistant;
- }
- return FileType::kUnknown;
+FileType GetFileType(const StringPiece& path) {
+ struct stat sb;
+ if (stat(path.data(), &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ return FileType::kNonexistant;
}
+ return FileType::kUnknown;
+ }
- if (S_ISREG(sb.st_mode)) {
- return FileType::kRegular;
- } else if (S_ISDIR(sb.st_mode)) {
- return FileType::kDirectory;
- } else if (S_ISCHR(sb.st_mode)) {
- return FileType::kCharDev;
- } else if (S_ISBLK(sb.st_mode)) {
- return FileType::kBlockDev;
- } else if (S_ISFIFO(sb.st_mode)) {
- return FileType::kFifo;
+ if (S_ISREG(sb.st_mode)) {
+ return FileType::kRegular;
+ } else if (S_ISDIR(sb.st_mode)) {
+ return FileType::kDirectory;
+ } else if (S_ISCHR(sb.st_mode)) {
+ return FileType::kCharDev;
+ } else if (S_ISBLK(sb.st_mode)) {
+ return FileType::kBlockDev;
+ } else if (S_ISFIFO(sb.st_mode)) {
+ return FileType::kFifo;
#if defined(S_ISLNK)
- } else if (S_ISLNK(sb.st_mode)) {
- return FileType::kSymlink;
+ } else if (S_ISLNK(sb.st_mode)) {
+ return FileType::kSymlink;
#endif
#if defined(S_ISSOCK)
- } else if (S_ISSOCK(sb.st_mode)) {
- return FileType::kSocket;
+ } else if (S_ISSOCK(sb.st_mode)) {
+ return FileType::kSocket;
#endif
- } else {
- return FileType::kUnknown;
- }
+ } else {
+ return FileType::kUnknown;
+ }
}
-std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
- DIR* dir = opendir(root.data());
- if (dir == nullptr) {
- if (outError) {
- std::stringstream errorStr;
- errorStr << "unable to open file: " << strerror(errno);
- *outError = errorStr.str();
- return {};
- }
- }
-
- std::vector<std::string> files;
- dirent* entry;
- while ((entry = readdir(dir))) {
- files.emplace_back(entry->d_name);
- }
-
- closedir(dir);
- return files;
-}
-
-inline static int mkdirImpl(const StringPiece& path) {
+inline static int MkdirImpl(const StringPiece& path) {
#ifdef _WIN32
- return _mkdir(path.toString().c_str());
+ return _mkdir(path.ToString().c_str());
#else
- return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+ return mkdir(path.ToString().c_str(),
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
#endif
}
bool mkdirs(const StringPiece& path) {
- const char* start = path.begin();
- const char* end = path.end();
- for (const char* current = start; current != end; ++current) {
- if (*current == sDirSep && current != start) {
- StringPiece parentPath(start, current - start);
- int result = mkdirImpl(parentPath);
- if (result < 0 && errno != EEXIST) {
- return false;
- }
- }
- }
- return mkdirImpl(path) == 0 || errno == EEXIST;
-}
-
-StringPiece getStem(const StringPiece& path) {
- const char* start = path.begin();
- const char* end = path.end();
- for (const char* current = end - 1; current != start - 1; --current) {
- if (*current == sDirSep) {
- return StringPiece(start, current - start);
- }
- }
- return {};
-}
-
-StringPiece getFilename(const StringPiece& path) {
- const char* end = path.end();
- const char* lastDirSep = path.begin();
- for (const char* c = path.begin(); c != end; ++c) {
- if (*c == sDirSep) {
- lastDirSep = c + 1;
- }
- }
- return StringPiece(lastDirSep, end - lastDirSep);
-}
-
-StringPiece getExtension(const StringPiece& path) {
- StringPiece filename = getFilename(path);
- const char* const end = filename.end();
- const char* c = std::find(filename.begin(), end, '.');
- if (c != end) {
- return StringPiece(c, end - c);
- }
- return {};
-}
-
-void appendPath(std::string* base, StringPiece part) {
- assert(base);
- const bool baseHasTrailingSep = (!base->empty() && *(base->end() - 1) == sDirSep);
- const bool partHasLeadingSep = (!part.empty() && *(part.begin()) == sDirSep);
- if (baseHasTrailingSep && partHasLeadingSep) {
- // Remove the part's leading sep
- part = part.substr(1, part.size() - 1);
- } else if (!baseHasTrailingSep && !partHasLeadingSep) {
- // None of the pieces has a separator.
- *base += sDirSep;
- }
- base->append(part.data(), part.size());
-}
-
-std::string packageToPath(const StringPiece& package) {
- std::string outPath;
- for (StringPiece part : util::tokenize(package, '.')) {
- appendPath(&outPath, part);
- }
- return outPath;
-}
-
-Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
- std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
- if (!f) {
- if (outError) *outError = strerror(errno);
- return {};
- }
-
- int fd = fileno(f.get());
-
- struct stat fileStats = {};
- if (fstat(fd, &fileStats) != 0) {
- if (outError) *outError = strerror(errno);
- return {};
- }
-
- android::FileMap fileMap;
- if (fileStats.st_size == 0) {
- // mmap doesn't like a length of 0. Instead we return an empty FileMap.
- return std::move(fileMap);
- }
-
- if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
- if (outError) *outError = strerror(errno);
- return {};
- }
- return std::move(fileMap);
-}
-
-bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList,
- std::string* outError) {
- std::string contents;
- if (!android::base::ReadFileToString(path.toString(), &contents)) {
- if (outError) *outError = "failed to read argument-list file";
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = start; current != end; ++current) {
+ if (*current == sDirSep && current != start) {
+ StringPiece parent_path(start, current - start);
+ int result = MkdirImpl(parent_path);
+ if (result < 0 && errno != EEXIST) {
return false;
+ }
}
-
- for (StringPiece line : util::tokenize(contents, ' ')) {
- line = util::trimWhitespace(line);
- if (!line.empty()) {
- outArgList->push_back(line.toString());
- }
- }
- return true;
+ }
+ return MkdirImpl(path) == 0 || errno == EEXIST;
}
-bool FileFilter::setPattern(const StringPiece& pattern) {
- mPatternTokens = util::splitAndLowercase(pattern, ':');
- return true;
+StringPiece GetStem(const StringPiece& path) {
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = end - 1; current != start - 1; --current) {
+ if (*current == sDirSep) {
+ return StringPiece(start, current - start);
+ }
+ }
+ return {};
+}
+
+StringPiece GetFilename(const StringPiece& path) {
+ const char* end = path.end();
+ const char* last_dir_sep = path.begin();
+ for (const char* c = path.begin(); c != end; ++c) {
+ if (*c == sDirSep) {
+ last_dir_sep = c + 1;
+ }
+ }
+ return StringPiece(last_dir_sep, end - last_dir_sep);
+}
+
+StringPiece GetExtension(const StringPiece& path) {
+ StringPiece filename = GetFilename(path);
+ const char* const end = filename.end();
+ const char* c = std::find(filename.begin(), end, '.');
+ if (c != end) {
+ return StringPiece(c, end - c);
+ }
+ return {};
+}
+
+void AppendPath(std::string* base, StringPiece part) {
+ CHECK(base != nullptr);
+ const bool base_has_trailing_sep =
+ (!base->empty() && *(base->end() - 1) == sDirSep);
+ const bool part_has_leading_sep =
+ (!part.empty() && *(part.begin()) == sDirSep);
+ if (base_has_trailing_sep && part_has_leading_sep) {
+ // Remove the part's leading sep
+ part = part.substr(1, part.size() - 1);
+ } else if (!base_has_trailing_sep && !part_has_leading_sep) {
+ // None of the pieces has a separator.
+ *base += sDirSep;
+ }
+ base->append(part.data(), part.size());
+}
+
+std::string PackageToPath(const StringPiece& package) {
+ std::string out_path;
+ for (StringPiece part : util::Tokenize(package, '.')) {
+ AppendPath(&out_path, part);
+ }
+ return out_path;
+}
+
+Maybe<android::FileMap> MmapPath(const StringPiece& path,
+ std::string* out_error) {
+ std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"),
+ fclose};
+ if (!f) {
+ if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
+ return {};
+ }
+
+ int fd = fileno(f.get());
+
+ struct stat filestats = {};
+ if (fstat(fd, &filestats) != 0) {
+ if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
+ return {};
+ }
+
+ android::FileMap filemap;
+ if (filestats.st_size == 0) {
+ // mmap doesn't like a length of 0. Instead we return an empty FileMap.
+ return std::move(filemap);
+ }
+
+ if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) {
+ if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
+ return {};
+ }
+ return std::move(filemap);
+}
+
+bool AppendArgsFromFile(const StringPiece& path,
+ std::vector<std::string>* out_arglist,
+ std::string* out_error) {
+ std::string contents;
+ if (!android::base::ReadFileToString(path.ToString(), &contents)) {
+ if (out_error) *out_error = "failed to read argument-list file";
+ return false;
+ }
+
+ for (StringPiece line : util::Tokenize(contents, ' ')) {
+ line = util::TrimWhitespace(line);
+ if (!line.empty()) {
+ out_arglist->push_back(line.ToString());
+ }
+ }
+ return true;
+}
+
+bool FileFilter::SetPattern(const StringPiece& pattern) {
+ pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
+ return true;
}
bool FileFilter::operator()(const std::string& filename, FileType type) const {
- if (filename == "." || filename == "..") {
- return false;
+ if (filename == "." || filename == "..") {
+ return false;
+ }
+
+ const char kDir[] = "dir";
+ const char kFile[] = "file";
+ const size_t filename_len = filename.length();
+ bool chatty = true;
+ for (const std::string& token : pattern_tokens_) {
+ const char* token_str = token.c_str();
+ if (*token_str == '!') {
+ chatty = false;
+ token_str++;
}
- const char kDir[] = "dir";
- const char kFile[] = "file";
- const size_t filenameLen = filename.length();
- bool chatty = true;
- for (const std::string& token : mPatternTokens) {
- const char* tokenStr = token.c_str();
- if (*tokenStr == '!') {
- chatty = false;
- tokenStr++;
- }
-
- if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
- if (type != FileType::kDirectory) {
- continue;
- }
- tokenStr += sizeof(kDir);
- }
-
- if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
- if (type != FileType::kRegular) {
- continue;
- }
- tokenStr += sizeof(kFile);
- }
-
- bool ignore = false;
- size_t n = strlen(tokenStr);
- if (*tokenStr == '*') {
- // Math suffix.
- tokenStr++;
- n--;
- if (n <= filenameLen) {
- ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
- }
- } else if (n > 1 && tokenStr[n - 1] == '*') {
- // Match prefix.
- ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
- } else {
- ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
- }
-
- if (ignore) {
- if (chatty) {
- mDiag->warn(DiagMessage() << "skipping "
- << (type == FileType::kDirectory ? "dir '" : "file '")
- << filename << "' due to ignore pattern '"
- << token << "'");
- }
- return false;
- }
+ if (strncasecmp(token_str, kDir, sizeof(kDir)) == 0) {
+ if (type != FileType::kDirectory) {
+ continue;
+ }
+ token_str += sizeof(kDir);
}
- return true;
+
+ if (strncasecmp(token_str, kFile, sizeof(kFile)) == 0) {
+ if (type != FileType::kRegular) {
+ continue;
+ }
+ token_str += sizeof(kFile);
+ }
+
+ bool ignore = false;
+ size_t n = strlen(token_str);
+ if (*token_str == '*') {
+ // Math suffix.
+ token_str++;
+ n--;
+ if (n <= filename_len) {
+ ignore =
+ strncasecmp(token_str, filename.c_str() + filename_len - n, n) == 0;
+ }
+ } else if (n > 1 && token_str[n - 1] == '*') {
+ // Match prefix.
+ ignore = strncasecmp(token_str, filename.c_str(), n - 1) == 0;
+ } else {
+ ignore = strcasecmp(token_str, filename.c_str()) == 0;
+ }
+
+ if (ignore) {
+ if (chatty) {
+ diag_->Warn(DiagMessage()
+ << "skipping "
+ << (type == FileType::kDirectory ? "dir '" : "file '")
+ << filename << "' due to ignore pattern '" << token << "'");
+ }
+ return false;
+ }
+ }
+ return true;
}
-} // namespace file
-} // namespace aapt
+} // namespace file
+} // namespace aapt
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 52c2052..a157dbd 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -17,18 +17,18 @@
#ifndef AAPT_FILES_H
#define AAPT_FILES_H
-#include "Diagnostics.h"
-#include "Maybe.h"
-#include "Source.h"
-
-#include "util/StringPiece.h"
-
-#include <utils/FileMap.h>
-#include <cassert>
#include <memory>
#include <string>
#include <vector>
+#include "android-base/macros.h"
+#include "utils/FileMap.h"
+
+#include "Diagnostics.h"
+#include "Maybe.h"
+#include "Source.h"
+#include "util/StringPiece.h"
+
namespace aapt {
namespace file {
@@ -39,29 +39,23 @@
#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);
-
-/*
- * Lists files under the directory `root`. Files are listed
- * with just their leaf (filename) names.
- */
-std::vector<std::string> listFiles(const StringPiece& root);
+FileType GetFileType(const StringPiece& path);
/*
* Appends a path to `base`, separated by the directory separator.
*/
-void appendPath(std::string* base, StringPiece part);
+void AppendPath(std::string* base, StringPiece part);
/*
* Makes all the directories in `path`. The last element in the path
@@ -72,73 +66,75 @@
/**
* Returns all but the last part of the path.
*/
-StringPiece getStem(const StringPiece& path);
+StringPiece GetStem(const StringPiece& path);
/**
* Returns the last part of the path with extension.
*/
-StringPiece getFilename(const StringPiece& path);
+StringPiece GetFilename(const StringPiece& path);
/**
* Returns the extension of the path. This is the entire string after
* the first '.' of the last part of the path.
*/
-StringPiece getExtension(const StringPiece& path);
+StringPiece GetExtension(const StringPiece& path);
/**
* Converts a package name (com.android.app) to a path: com/android/app
*/
-std::string packageToPath(const StringPiece& package);
+std::string PackageToPath(const StringPiece& package);
/**
* 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* out_error);
/**
* Reads the file at path and appends each line to the outArgList vector.
*/
-bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList,
- std::string* outError);
+bool AppendArgsFromFile(const StringPiece& path,
+ std::vector<std::string>* out_arglist,
+ std::string* out_error);
/*
* Filter that determines which resource files/directories are
* processed by AAPT. Takes a pattern string supplied by the user.
- * Pattern format is specified in the
- * FileFilter::setPattern(const std::string&) method.
+ * Pattern format is specified in the FileFilter::SetPattern() method.
*/
class FileFilter {
-public:
- explicit FileFilter(IDiagnostics* diag) : mDiag(diag) {
- }
+ public:
+ explicit FileFilter(IDiagnostics* diag) : diag_(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:
+ DISALLOW_COPY_AND_ASSIGN(FileFilter);
+
+ IDiagnostics* diag_;
+ std::vector<std::string> pattern_tokens_;
};
-} // namespace file
-} // namespace aapt
+} // namespace file
+} // namespace aapt
-#endif // AAPT_FILES_H
+#endif // AAPT_FILES_H
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index efb0459..219c183 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -14,45 +14,46 @@
* limitations under the License.
*/
-#include "test/Test.h"
#include "util/Files.h"
#include <sstream>
+#include "test/Test.h"
+
namespace aapt {
namespace file {
class FilesTest : public ::testing::Test {
-public:
- void SetUp() override {
- std::stringstream builder;
- builder << "hello" << sDirSep << "there";
- mExpectedPath = builder.str();
- }
+ public:
+ void SetUp() override {
+ std::stringstream builder;
+ builder << "hello" << sDirSep << "there";
+ expected_path_ = builder.str();
+ }
-protected:
- std::string mExpectedPath;
+ protected:
+ std::string expected_path_;
};
-TEST_F(FilesTest, appendPath) {
- std::string base = "hello";
- appendPath(&base, "there");
- EXPECT_EQ(mExpectedPath, base);
+TEST_F(FilesTest, AppendPath) {
+ std::string base = "hello";
+ AppendPath(&base, "there");
+ EXPECT_EQ(expected_path_, base);
}
-TEST_F(FilesTest, appendPathWithLeadingOrTrailingSeparators) {
- std::string base = "hello/";
- appendPath(&base, "there");
- EXPECT_EQ(mExpectedPath, base);
+TEST_F(FilesTest, AppendPathWithLeadingOrTrailingSeparators) {
+ std::string base = "hello/";
+ AppendPath(&base, "there");
+ EXPECT_EQ(expected_path_, base);
- base = "hello";
- appendPath(&base, "/there");
- EXPECT_EQ(mExpectedPath, base);
+ base = "hello";
+ AppendPath(&base, "/there");
+ EXPECT_EQ(expected_path_, base);
- base = "hello/";
- appendPath(&base, "/there");
- EXPECT_EQ(mExpectedPath, base);
+ base = "hello/";
+ AppendPath(&base, "/there");
+ EXPECT_EQ(expected_path_, base);
}
-} // namespace files
-} // namespace aapt
+} // namespace files
+} // namespace aapt
diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h
index b1f9e9d..59858e4 100644
--- a/tools/aapt2/util/ImmutableMap.h
+++ b/tools/aapt2/util/ImmutableMap.h
@@ -17,68 +17,66 @@
#ifndef AAPT_UTIL_IMMUTABLEMAP_H
#define AAPT_UTIL_IMMUTABLEMAP_H
-#include "util/TypeTraits.h"
-
#include <utility>
#include <vector>
+#include "util/TypeTraits.h"
+
namespace aapt {
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;
+ public:
+ using const_iterator =
+ typename std::vector<std::pair<TKey, TValue>>::const_iterator;
- explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) : mData(std::move(data)) {
+ ImmutableMap(ImmutableMap&&) = default;
+ ImmutableMap& operator=(ImmutableMap&&) = default;
+
+ 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 end_iter = end();
+ auto iter = std::lower_bound(data_.begin(), end_iter, key, cmp);
+ if (iter == end_iter || iter->first == key) {
+ return iter;
}
+ return end_iter;
+ }
-public:
- using const_iterator = typename decltype(mData)::const_iterator;
+ const_iterator begin() const { return data_.begin(); }
- ImmutableMap(ImmutableMap&&) = default;
- ImmutableMap& operator=(ImmutableMap&&) = default;
+ const_iterator end() const { return data_.end(); }
- ImmutableMap(const ImmutableMap&) = delete;
- ImmutableMap& operator=(const ImmutableMap&) = delete;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ImmutableMap);
- 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()));
- }
+ explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data)
+ : data_(std::move(data)) {}
- 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();
- }
+ std::vector<std::pair<TKey, TValue>> data_;
};
-} // 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..b43f8e8 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -17,12 +17,13 @@
#ifndef AAPT_MAYBE_H
#define AAPT_MAYBE_H
-#include "util/TypeTraits.h"
-
-#include <cassert>
#include <type_traits>
#include <utility>
+#include "android-base/logging.h"
+
+#include "util/TypeTraits.h"
+
namespace aapt {
/**
@@ -32,303 +33,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 value_or_default(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 nothing_;
- typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
+ typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
};
template <typename T>
-Maybe<T>::Maybe()
-: mNothing(true) {
-}
+Maybe<T>::Maybe() : nothing_(true) {}
template <typename T>
Maybe<T>::~Maybe() {
- if (!mNothing) {
- destroy();
- }
+ if (!nothing_) {
+ 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) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ new (&storage_) T(reinterpret_cast<const T&>(rhs.storage_));
+ }
}
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) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_));
+ }
}
template <typename T>
-Maybe<T>::Maybe(Maybe&& rhs)
-: mNothing(rhs.mNothing) {
- if (!rhs.mNothing) {
- rhs.mNothing = true;
+Maybe<T>::Maybe(Maybe&& rhs) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ rhs.nothing_ = true;
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage)));
- rhs.destroy();
- }
+ // Move the value from rhs.
+ new (&storage_) T(std::move(reinterpret_cast<T&>(rhs.storage_)));
+ 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) : nothing_(rhs.nothing_) {
+ if (!rhs.nothing_) {
+ rhs.nothing_ = true;
- // Move the value from rhs.
- new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
- rhs.destroy();
- }
+ // Move the value from rhs.
+ new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_)));
+ 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 (nothing_ && rhs.nothing_) {
+ // Both are nothing, nothing to do.
return *this;
+ } else if (!nothing_ && !rhs.nothing_) {
+ // We both are something, so assign rhs to us.
+ reinterpret_cast<T&>(storage_) = reinterpret_cast<const U&>(rhs.storage_);
+ } else if (nothing_) {
+ // We are nothing but rhs is something.
+ nothing_ = rhs.nothing_;
+
+ // Copy the value from rhs.
+ new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_));
+ } else {
+ // We are something but rhs is nothing, so destroy our value.
+ nothing_ = rhs.nothing_;
+ 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 (nothing_ && rhs.nothing_) {
+ // Both are nothing, nothing to do.
return *this;
+ } else if (!nothing_ && !rhs.nothing_) {
+ // We both are something, so move assign rhs to us.
+ rhs.nothing_ = true;
+ reinterpret_cast<T&>(storage_) =
+ std::move(reinterpret_cast<U&>(rhs.storage_));
+ rhs.destroy();
+ } else if (nothing_) {
+ // We are nothing but rhs is something.
+ nothing_ = false;
+ rhs.nothing_ = true;
+
+ // Move the value from rhs.
+ new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_)));
+ rhs.destroy();
+ } else {
+ // We are something but rhs is nothing, so destroy our value.
+ nothing_ = 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) : nothing_(false) {
+ new (&storage_) T(value);
}
template <typename T>
-Maybe<T>::Maybe(T&& value)
-: mNothing(false) {
- new (&mStorage) T(std::forward<T>(value));
+Maybe<T>::Maybe(T&& value) : nothing_(false) {
+ new (&storage_) T(std::forward<T>(value));
}
template <typename T>
Maybe<T>::operator bool() const {
- return !mNothing;
+ return !nothing_;
}
template <typename T>
T& Maybe<T>::value() {
- assert(!mNothing && "Maybe<T>::value() called on Nothing");
- return reinterpret_cast<T&>(mStorage);
+ CHECK(!nothing_) << "Maybe<T>::value() called on Nothing";
+ return reinterpret_cast<T&>(storage_);
}
template <typename T>
const T& Maybe<T>::value() const {
- assert(!mNothing && "Maybe<T>::value() called on Nothing");
- return reinterpret_cast<const T&>(mStorage);
+ CHECK(!nothing_) << "Maybe<T>::value() called on Nothing";
+ return reinterpret_cast<const T&>(storage_);
}
template <typename T>
-T Maybe<T>::valueOrDefault(const T& def) const {
- if (mNothing) {
- return def;
- }
- return reinterpret_cast<const T&>(mStorage);
+T Maybe<T>::value_or_default(const T& def) const {
+ if (nothing_) {
+ return def;
+ }
+ return reinterpret_cast<const T&>(storage_);
}
template <typename T>
void Maybe<T>::destroy() {
- reinterpret_cast<T&>(mStorage).~T();
+ reinterpret_cast<T&>(storage_).~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/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index 5d42dc3..ca14793 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -14,122 +14,116 @@
* limitations under the License.
*/
-#include "test/Common.h"
#include "util/Maybe.h"
-#include <gtest/gtest.h>
#include <string>
+#include "test/Test.h"
+
namespace aapt {
struct Dummy {
- Dummy() {
- data = new int;
- *data = 1;
- std::cerr << "Construct Dummy{0x" << (void *) this
- << "} with data=0x" << (void*) data
- << std::endl;
+ Dummy() {
+ data = new int;
+ *data = 1;
+ std::cerr << "Construct Dummy{0x" << (void*)this << "} with data=0x"
+ << (void*)data << std::endl;
+ }
+
+ Dummy(const Dummy& rhs) {
+ data = nullptr;
+ if (rhs.data) {
+ data = new int;
+ *data = *rhs.data;
}
+ std::cerr << "CopyConstruct Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ }
- Dummy(const Dummy& rhs) {
- data = nullptr;
- if (rhs.data) {
- data = new int;
- *data = *rhs.data;
- }
- std::cerr << "CopyConstruct Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
+ Dummy(Dummy&& rhs) {
+ data = rhs.data;
+ rhs.data = nullptr;
+ std::cerr << "MoveConstruct Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ }
+
+ Dummy& operator=(const Dummy& rhs) {
+ delete data;
+ data = nullptr;
+
+ if (rhs.data) {
+ data = new int;
+ *data = *rhs.data;
}
+ std::cerr << "CopyAssign Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ return *this;
+ }
- Dummy(Dummy&& rhs) {
- data = rhs.data;
- rhs.data = nullptr;
- std::cerr << "MoveConstruct Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- }
+ Dummy& operator=(Dummy&& rhs) {
+ delete data;
+ data = rhs.data;
+ rhs.data = nullptr;
+ std::cerr << "MoveAssign Dummy{0x" << (void*)this << "} from Dummy{0x"
+ << (const void*)&rhs << "}" << std::endl;
+ return *this;
+ }
- Dummy& operator=(const Dummy& rhs) {
- delete data;
- data = nullptr;
+ ~Dummy() {
+ std::cerr << "Destruct Dummy{0x" << (void*)this << "} with data=0x"
+ << (void*)data << std::endl;
+ delete data;
+ }
- if (rhs.data) {
- data = new int;
- *data = *rhs.data;
- }
- std::cerr << "CopyAssign Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- return *this;
- }
-
- Dummy& operator=(Dummy&& rhs) {
- delete data;
- data = rhs.data;
- rhs.data = nullptr;
- std::cerr << "MoveAssign Dummy{0x" << (void *) this
- << "} from Dummy{0x" << (const void*) &rhs
- << "}" << std::endl;
- return *this;
- }
-
- ~Dummy() {
- std::cerr << "Destruct Dummy{0x" << (void *) this
- << "} with data=0x" << (void*) data
- << std::endl;
- delete data;
- }
-
- int* data;
+ int* data;
};
TEST(MaybeTest, MakeNothing) {
- Maybe<int> val = make_nothing<int>();
- AAPT_EXPECT_FALSE(val);
+ Maybe<int> val = make_nothing<int>();
+ AAPT_EXPECT_FALSE(val);
- Maybe<std::string> val2 = make_nothing<std::string>();
- AAPT_EXPECT_FALSE(val2);
+ Maybe<std::string> val2 = make_nothing<std::string>();
+ AAPT_EXPECT_FALSE(val2);
- val2 = make_nothing<std::string>();
- AAPT_EXPECT_FALSE(val2);
+ val2 = make_nothing<std::string>();
+ AAPT_EXPECT_FALSE(val2);
}
TEST(MaybeTest, MakeSomething) {
- Maybe<int> val = make_value(23);
- AAPT_ASSERT_TRUE(val);
- EXPECT_EQ(23, val.value());
+ Maybe<int> val = make_value(23);
+ AAPT_ASSERT_TRUE(val);
+ EXPECT_EQ(23, val.value());
- Maybe<std::string> val2 = make_value(std::string("hey"));
- AAPT_ASSERT_TRUE(val2);
- EXPECT_EQ(std::string("hey"), val2.value());
+ Maybe<std::string> val2 = make_value(std::string("hey"));
+ AAPT_ASSERT_TRUE(val2);
+ EXPECT_EQ(std::string("hey"), val2.value());
}
TEST(MaybeTest, Lifecycle) {
- Maybe<Dummy> val = make_nothing<Dummy>();
+ Maybe<Dummy> val = make_nothing<Dummy>();
- Maybe<Dummy> val2 = make_value(Dummy());
+ Maybe<Dummy> val2 = make_value(Dummy());
}
TEST(MaybeTest, MoveAssign) {
- Maybe<Dummy> val;
- {
- Maybe<Dummy> val2 = Dummy();
- val = std::move(val2);
- }
+ Maybe<Dummy> val;
+ {
+ Maybe<Dummy> val2 = Dummy();
+ val = std::move(val2);
+ }
}
TEST(MaybeTest, Equality) {
- Maybe<int> a = 1;
- Maybe<int> b = 1;
- Maybe<int> c;
+ Maybe<int> a = 1;
+ Maybe<int> b = 1;
+ Maybe<int> c;
- Maybe<int> emptyA, emptyB;
+ Maybe<int> emptyA, emptyB;
- EXPECT_EQ(a, b);
- EXPECT_EQ(b, a);
- EXPECT_NE(a, c);
- EXPECT_EQ(emptyA, emptyB);
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+ EXPECT_NE(a, c);
+ EXPECT_EQ(emptyA, emptyB);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h
index 4300a67..5144b1f 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -19,9 +19,10 @@
#include <ostream>
#include <string>
-#include <utils/JenkinsHash.h>
-#include <utils/String8.h>
-#include <utils/Unicode.h>
+
+#include "utils/JenkinsHash.h"
+#include "utils/String8.h"
+#include "utils/Unicode.h"
namespace aapt {
@@ -35,42 +36,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;
- 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);
+ // End of string marker.
+ constexpr static const size_t npos = static_cast<size_t>(-1);
- BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
- BasicStringPiece<TChar>& assign(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> substr(size_t start, size_t len) const;
- BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const;
+ BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
+ BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
- const TChar* data() const;
- size_t length() const;
- size_t size() const;
- bool empty() const;
- std::basic_string<TChar> toString() 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;
- 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 TChar* data() const;
+ size_t length() const;
+ size_t size() const;
+ bool empty() const;
+ std::basic_string<TChar> ToString() const;
- const_iterator begin() const;
- const_iterator end() 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;
-private:
- const TChar* mData;
- size_t mLength;
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ const TChar* data_;
+ size_t length_;
};
using StringPiece = BasicStringPiece<char>;
@@ -81,194 +86,213 @@
//
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) {
-}
+constexpr const size_t BasicStringPiece<TChar>::npos;
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) :
- mData(str.mData), mLength(str.mLength) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece()
+ : data_(nullptr), length_(0) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) :
- mData(str.data()), mLength(str.length()) {
-}
+inline BasicStringPiece<TChar>::BasicStringPiece(
+ const BasicStringPiece<TChar>& str)
+ : data_(str.data_), length_(str.length_) {}
+
+template <typename TChar>
+inline BasicStringPiece<TChar>::BasicStringPiece(
+ const std::basic_string<TChar>& str)
+ : data_(str.data()), length_(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)
+ : data_(str), length_(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)
+ : data_(str), length_(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)
+ : data_(str), length_(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) {
+ data_ = rhs.data_;
+ length_ = rhs.length_;
+ 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 (start + len > mLength) {
- return BasicStringPiece<TChar>();
- }
- return BasicStringPiece<TChar>(mData + start, len);
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(
+ const TChar* str, size_t len) {
+ data_ = str;
+ length_ = 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 = length_ - start;
+ }
+
+ if (start > length_ || start + len > length_) {
+ return BasicStringPiece<TChar>();
+ }
+ return BasicStringPiece<TChar>(data_ + 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 data_;
}
template <typename TChar>
inline size_t BasicStringPiece<TChar>::length() const {
- return mLength;
+ return length_;
}
template <typename TChar>
inline size_t BasicStringPiece<TChar>::size() const {
- return mLength;
+ return length_;
}
template <typename TChar>
inline bool BasicStringPiece<TChar>::empty() const {
- return mLength == 0;
+ return length_ == 0;
}
template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
- return std::basic_string<TChar>(mData, mLength);
+inline std::basic_string<TChar> BasicStringPiece<TChar>::ToString() const {
+ return std::basic_string<TChar>(data_, length_);
}
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 (!data_ || !rhs.data_) {
+ return false;
+ }
+ if (rhs.length_ > length_) {
+ return false;
+ }
+ return strstr(data_, rhs.data_) != 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 = data_ != nullptr ? data_ : &nullStr;
+ const char* e1 = b1 + length_;
+ const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
+ const char* e2 = b2 + rhs.length_;
- 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>(length_ - rhs.length_);
}
-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 (!data_ || !rhs.data_) {
+ return false;
+ }
+ if (rhs.length_ > length_) {
+ return false;
+ }
+ return strstr16(data_, rhs.data_) != 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 = data_ != nullptr ? data_ : &nullStr;
+ const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
+ return strzcmp16(b1, length_, b2, rhs.length_);
}
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 data_;
}
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 data_ + length_;
}
-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/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp
index a87065a..048961d 100644
--- a/tools/aapt2/util/StringPiece_test.cpp
+++ b/tools/aapt2/util/StringPiece_test.cpp
@@ -14,81 +14,82 @@
* limitations under the License.
*/
+#include "util/StringPiece.h"
+
#include <algorithm>
-#include <gtest/gtest.h>
#include <string>
#include <vector>
-#include "util/StringPiece.h"
+#include "test/Test.h"
namespace aapt {
TEST(StringPieceTest, CompareNonNullTerminatedPiece) {
- StringPiece a("hello world", 5);
- StringPiece b("hello moon", 5);
- EXPECT_EQ(a, b);
+ StringPiece a("hello world", 5);
+ StringPiece b("hello moon", 5);
+ EXPECT_EQ(a, b);
- StringPiece16 a16(u"hello world", 5);
- StringPiece16 b16(u"hello moon", 5);
- EXPECT_EQ(a16, b16);
+ StringPiece16 a16(u"hello world", 5);
+ StringPiece16 b16(u"hello moon", 5);
+ EXPECT_EQ(a16, b16);
}
TEST(StringPieceTest, PiecesHaveCorrectSortOrder) {
- std::string testing("testing");
- std::string banana("banana");
- std::string car("car");
+ std::string testing("testing");
+ std::string banana("banana");
+ std::string car("car");
- EXPECT_TRUE(StringPiece(testing) > banana);
- EXPECT_TRUE(StringPiece(testing) > car);
- EXPECT_TRUE(StringPiece(banana) < testing);
- EXPECT_TRUE(StringPiece(banana) < car);
- EXPECT_TRUE(StringPiece(car) < testing);
- EXPECT_TRUE(StringPiece(car) > banana);
+ EXPECT_TRUE(StringPiece(testing) > banana);
+ EXPECT_TRUE(StringPiece(testing) > car);
+ EXPECT_TRUE(StringPiece(banana) < testing);
+ EXPECT_TRUE(StringPiece(banana) < car);
+ EXPECT_TRUE(StringPiece(car) < testing);
+ EXPECT_TRUE(StringPiece(car) > banana);
}
TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
- std::string testing("testing");
- std::string banana("banana");
- std::string car("car");
+ std::string testing("testing");
+ std::string banana("banana");
+ std::string car("car");
- EXPECT_TRUE(StringPiece(testing) > banana);
- EXPECT_TRUE(StringPiece(testing) > car);
- EXPECT_TRUE(StringPiece(banana) < testing);
- EXPECT_TRUE(StringPiece(banana) < car);
- EXPECT_TRUE(StringPiece(car) < testing);
- EXPECT_TRUE(StringPiece(car) > banana);
+ EXPECT_TRUE(StringPiece(testing) > banana);
+ EXPECT_TRUE(StringPiece(testing) > car);
+ EXPECT_TRUE(StringPiece(banana) < testing);
+ EXPECT_TRUE(StringPiece(banana) < car);
+ EXPECT_TRUE(StringPiece(car) < testing);
+ EXPECT_TRUE(StringPiece(car) > banana);
}
TEST(StringPieceTest, ContainsOtherStringPiece) {
- StringPiece text("I am a leaf on the wind.");
- StringPiece startNeedle("I am");
- StringPiece endNeedle("wind.");
- StringPiece middleNeedle("leaf");
- StringPiece emptyNeedle("");
- StringPiece missingNeedle("soar");
- StringPiece longNeedle("This string is longer than the text.");
+ StringPiece text("I am a leaf on the wind.");
+ StringPiece start_needle("I am");
+ StringPiece end_needle("wind.");
+ StringPiece middle_needle("leaf");
+ StringPiece empty_needle("");
+ StringPiece missing_needle("soar");
+ StringPiece long_needle("This string is longer than the text.");
- EXPECT_TRUE(text.contains(startNeedle));
- EXPECT_TRUE(text.contains(endNeedle));
- EXPECT_TRUE(text.contains(middleNeedle));
- EXPECT_TRUE(text.contains(emptyNeedle));
- EXPECT_FALSE(text.contains(missingNeedle));
- EXPECT_FALSE(text.contains(longNeedle));
+ EXPECT_TRUE(text.contains(start_needle));
+ EXPECT_TRUE(text.contains(end_needle));
+ EXPECT_TRUE(text.contains(middle_needle));
+ EXPECT_TRUE(text.contains(empty_needle));
+ EXPECT_FALSE(text.contains(missing_needle));
+ EXPECT_FALSE(text.contains(long_needle));
- StringPiece16 text16(u"I am a leaf on the wind.");
- StringPiece16 startNeedle16(u"I am");
- StringPiece16 endNeedle16(u"wind.");
- StringPiece16 middleNeedle16(u"leaf");
- StringPiece16 emptyNeedle16(u"");
- StringPiece16 missingNeedle16(u"soar");
- StringPiece16 longNeedle16(u"This string is longer than the text.");
+ StringPiece16 text16(u"I am a leaf on the wind.");
+ StringPiece16 start_needle16(u"I am");
+ StringPiece16 end_needle16(u"wind.");
+ StringPiece16 middle_needle16(u"leaf");
+ StringPiece16 empty_needle16(u"");
+ StringPiece16 missing_needle16(u"soar");
+ StringPiece16 long_needle16(u"This string is longer than the text.");
- EXPECT_TRUE(text16.contains(startNeedle16));
- EXPECT_TRUE(text16.contains(endNeedle16));
- EXPECT_TRUE(text16.contains(middleNeedle16));
- EXPECT_TRUE(text16.contains(emptyNeedle16));
- EXPECT_FALSE(text16.contains(missingNeedle16));
- EXPECT_FALSE(text16.contains(longNeedle16));
+ EXPECT_TRUE(text16.contains(start_needle16));
+ EXPECT_TRUE(text16.contains(end_needle16));
+ EXPECT_TRUE(text16.contains(middle_needle16));
+ EXPECT_TRUE(text16.contains(empty_needle16));
+ EXPECT_FALSE(text16.contains(missing_needle16));
+ EXPECT_FALSE(text16.contains(long_needle16));
}
-} // namespace aapt
+} // namespace aapt
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.cpp b/tools/aapt2/util/Util.cpp
index b0bec62..d5c0c8a 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -14,559 +14,558 @@
* limitations under the License.
*/
+#include "util/Util.h"
#include "util/BigBuffer.h"
#include "util/Maybe.h"
#include "util/StringPiece.h"
-#include "util/Util.h"
+#include <utils/Unicode.h>
#include <algorithm>
#include <ostream>
#include <string>
-#include <utils/Unicode.h>
#include <vector>
namespace aapt {
namespace util {
-static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
- const std::function<char(char)>& f) {
- std::vector<std::string> parts;
- const StringPiece::const_iterator end = std::end(str);
- StringPiece::const_iterator start = std::begin(str);
- StringPiece::const_iterator current;
- do {
- current = std::find(start, end, sep);
- parts.emplace_back(str.substr(start, current).toString());
- if (f) {
- std::string& part = parts.back();
- std::transform(part.begin(), part.end(), part.begin(), f);
- }
- start = current + 1;
- } while (current != end);
- return parts;
+static std::vector<std::string> SplitAndTransform(
+ const StringPiece& str, char sep, const std::function<char(char)>& f) {
+ std::vector<std::string> parts;
+ const StringPiece::const_iterator end = std::end(str);
+ StringPiece::const_iterator start = std::begin(str);
+ StringPiece::const_iterator current;
+ do {
+ current = std::find(start, end, sep);
+ parts.emplace_back(str.substr(start, current).ToString());
+ if (f) {
+ std::string& part = parts.back();
+ std::transform(part.begin(), part.end(), part.begin(), f);
+ }
+ start = current + 1;
+ } while (current != end);
+ return parts;
}
-std::vector<std::string> split(const StringPiece& str, char sep) {
- return splitAndTransform(str, sep, nullptr);
+std::vector<std::string> Split(const StringPiece& str, char sep) {
+ return SplitAndTransform(str, sep, nullptr);
}
-std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
- return splitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
+ return SplitAndTransform(str, sep, ::tolower);
}
-bool stringStartsWith(const StringPiece& str, const StringPiece& prefix) {
- if (str.size() < prefix.size()) {
- return false;
- }
- return str.substr(0, prefix.size()) == prefix;
+bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
+ if (str.size() < prefix.size()) {
+ return false;
+ }
+ return str.substr(0, prefix.size()) == prefix;
}
-bool stringEndsWith(const StringPiece& str, const StringPiece& suffix) {
- if (str.size() < suffix.size()) {
- return false;
- }
- return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
+bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
+ if (str.size() < suffix.size()) {
+ return false;
+ }
+ return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
}
-StringPiece trimWhitespace(const StringPiece& str) {
- if (str.size() == 0 || str.data() == nullptr) {
- return str;
- }
+StringPiece TrimWhitespace(const StringPiece& str) {
+ if (str.size() == 0 || str.data() == nullptr) {
+ return str;
+ }
- const char* start = str.data();
- const char* end = str.data() + str.length();
+ const char* start = str.data();
+ const char* end = str.data() + str.length();
- while (start != end && isspace(*start)) {
- start++;
- }
+ while (start != end && isspace(*start)) {
+ start++;
+ }
- while (end != start && isspace(*(end - 1))) {
- end--;
- }
+ while (end != start && isspace(*(end - 1))) {
+ end--;
+ }
- return StringPiece(start, end - start);
+ return StringPiece(start, end - start);
}
-StringPiece::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece& str,
- const StringPiece& allowedChars) {
- const auto endIter = str.end();
- for (auto iter = str.begin(); iter != endIter; ++iter) {
- char c = *iter;
- if ((c >= u'a' && c <= u'z') ||
- (c >= u'A' && c <= u'Z') ||
- (c >= u'0' && c <= u'9')) {
- continue;
- }
-
- bool match = false;
- for (char i : allowedChars) {
- if (c == i) {
- match = true;
- break;
- }
- }
-
- if (!match) {
- return iter;
- }
+StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
+ const StringPiece& str, const StringPiece& allowed_chars) {
+ const auto end_iter = str.end();
+ for (auto iter = str.begin(); iter != end_iter; ++iter) {
+ char c = *iter;
+ if ((c >= u'a' && c <= u'z') || (c >= u'A' && c <= u'Z') ||
+ (c >= u'0' && c <= u'9')) {
+ continue;
}
- return endIter;
+
+ bool match = false;
+ for (char i : allowed_chars) {
+ if (c == i) {
+ match = true;
+ break;
+ }
+ }
+
+ if (!match) {
+ return iter;
+ }
+ }
+ return end_iter;
}
-bool isJavaClassName(const StringPiece& str) {
- size_t pieces = 0;
- for (const StringPiece& piece : tokenize(str, '.')) {
- pieces++;
- if (piece.empty()) {
- return false;
- }
-
- // Can't have starting or trailing $ character.
- if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') {
- return false;
- }
-
- if (findNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) {
- return false;
- }
+bool IsJavaClassName(const StringPiece& str) {
+ size_t pieces = 0;
+ for (const StringPiece& piece : Tokenize(str, '.')) {
+ pieces++;
+ if (piece.empty()) {
+ return false;
}
- return pieces >= 2;
+
+ // Can't have starting or trailing $ character.
+ if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') {
+ return false;
+ }
+
+ if (FindNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) {
+ return false;
+ }
+ }
+ return pieces >= 2;
}
-bool isJavaPackageName(const StringPiece& str) {
- if (str.empty()) {
- return false;
+bool IsJavaPackageName(const StringPiece& str) {
+ if (str.empty()) {
+ return false;
+ }
+
+ size_t pieces = 0;
+ for (const StringPiece& piece : Tokenize(str, '.')) {
+ pieces++;
+ if (piece.empty()) {
+ return false;
}
- size_t pieces = 0;
- for (const StringPiece& piece : tokenize(str, '.')) {
- pieces++;
- if (piece.empty()) {
- return false;
- }
-
- if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') {
- return false;
- }
-
- if (findNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) {
- return false;
- }
+ if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') {
+ return false;
}
- return pieces >= 1;
+
+ if (FindNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) {
+ return false;
+ }
+ }
+ return pieces >= 1;
}
-Maybe<std::string> getFullyQualifiedClassName(const StringPiece& package,
- const StringPiece& className) {
- if (className.empty()) {
- return {};
- }
+Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
+ const StringPiece& classname) {
+ if (classname.empty()) {
+ return {};
+ }
- if (util::isJavaClassName(className)) {
- return className.toString();
- }
+ if (util::IsJavaClassName(classname)) {
+ return classname.ToString();
+ }
- if (package.empty()) {
- return {};
- }
+ if (package.empty()) {
+ return {};
+ }
- std::string result(package.data(), package.size());
- if (className.data()[0] != '.') {
- result += '.';
- }
+ std::string result(package.data(), package.size());
+ if (classname.data()[0] != '.') {
+ result += '.';
+ }
- result.append(className.data(), className.size());
- if (!isJavaClassName(result)) {
- return {};
- }
- return result;
+ result.append(classname.data(), classname.size());
+ if (!IsJavaClassName(result)) {
+ return {};
+ }
+ return result;
}
-static size_t consumeDigits(const char* start, const char* end) {
- const char* c = start;
- for (; c != end && *c >= '0' && *c <= '9'; c++) {}
- return static_cast<size_t>(c - start);
+static size_t ConsumeDigits(const char* start, const char* end) {
+ const char* c = start;
+ for (; c != end && *c >= '0' && *c <= '9'; c++) {
+ }
+ return static_cast<size_t>(c - start);
}
-bool verifyJavaStringFormat(const StringPiece& str) {
- const char* c = str.begin();
- const char* const end = str.end();
+bool VerifyJavaStringFormat(const StringPiece& str) {
+ const char* c = str.begin();
+ const char* const end = str.end();
- size_t argCount = 0;
- bool nonpositional = false;
- while (c != end) {
- if (*c == '%' && c + 1 < end) {
- c++;
+ size_t arg_count = 0;
+ bool nonpositional = false;
+ while (c != end) {
+ if (*c == '%' && c + 1 < end) {
+ c++;
- if (*c == '%') {
- c++;
- continue;
- }
+ if (*c == '%') {
+ c++;
+ continue;
+ }
- argCount++;
+ arg_count++;
- size_t numDigits = consumeDigits(c, end);
- if (numDigits > 0) {
- c += numDigits;
- if (c != end && *c != '$') {
- // The digits were a size, but not a positional argument.
- nonpositional = true;
- }
- } else if (*c == '<') {
- // Reusing last argument, bad idea since positions can be moved around
- // during translation.
- nonpositional = true;
-
- c++;
-
- // Optionally we can have a $ after
- if (c != end && *c == '$') {
- c++;
- }
- } else {
- nonpositional = true;
- }
-
- // Ignore size, width, flags, etc.
- while (c != end && (*c == '-' ||
- *c == '#' ||
- *c == '+' ||
- *c == ' ' ||
- *c == ',' ||
- *c == '(' ||
- (*c >= '0' && *c <= '9'))) {
- c++;
- }
-
- /*
- * This is a shortcut to detect strings that are going to Time.format()
- * instead of String.format()
- *
- * Comparison of String.format() and Time.format() args:
- *
- * String: ABC E GH ST X abcdefgh nost x
- * Time: DEFGHKMS W Za d hkm s w yz
- *
- * Therefore we know it's definitely Time if we have:
- * DFKMWZkmwyz
- */
- if (c != end) {
- switch (*c) {
- case 'D':
- case 'F':
- case 'K':
- case 'M':
- case 'W':
- case 'Z':
- case 'k':
- case 'm':
- case 'w':
- case 'y':
- case 'z':
- return true;
- }
- }
+ size_t num_digits = ConsumeDigits(c, end);
+ if (num_digits > 0) {
+ c += num_digits;
+ if (c != end && *c != '$') {
+ // The digits were a size, but not a positional argument.
+ nonpositional = true;
}
+ } else if (*c == '<') {
+ // Reusing last argument, bad idea since positions can be moved around
+ // during translation.
+ nonpositional = true;
- if (c != end) {
- c++;
+ c++;
+
+ // Optionally we can have a $ after
+ if (c != end && *c == '$') {
+ c++;
}
+ } else {
+ nonpositional = true;
+ }
+
+ // Ignore size, width, flags, etc.
+ while (c != end && (*c == '-' || *c == '#' || *c == '+' || *c == ' ' ||
+ *c == ',' || *c == '(' || (*c >= '0' && *c <= '9'))) {
+ c++;
+ }
+
+ /*
+ * This is a shortcut to detect strings that are going to Time.format()
+ * instead of String.format()
+ *
+ * Comparison of String.format() and Time.format() args:
+ *
+ * String: ABC E GH ST X abcdefgh nost x
+ * Time: DEFGHKMS W Za d hkm s w yz
+ *
+ * Therefore we know it's definitely Time if we have:
+ * DFKMWZkmwyz
+ */
+ if (c != end) {
+ switch (*c) {
+ case 'D':
+ case 'F':
+ case 'K':
+ case 'M':
+ case 'W':
+ case 'Z':
+ case 'k':
+ case 'm':
+ case 'w':
+ case 'y':
+ case 'z':
+ return true;
+ }
+ }
}
- if (argCount > 1 && nonpositional) {
- // Multiple arguments were specified, but some or all were non positional. Translated
- // strings may rearrange the order of the arguments, which will break the string.
- return false;
+ if (c != end) {
+ c++;
}
- return true;
+ }
+
+ if (arg_count > 1 && nonpositional) {
+ // Multiple arguments were specified, but some or all were non positional.
+ // Translated
+ // strings may rearrange the order of the arguments, which will break the
+ // string.
+ return false;
+ }
+ return true;
}
-static Maybe<std::string> parseUnicodeCodepoint(const char** start, const char* end) {
- char32_t code = 0;
- for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
- char c = **start;
- char32_t a;
- if (c >= '0' && c <= '9') {
- a = c - '0';
- } else if (c >= 'a' && c <= 'f') {
- a = c - 'a' + 10;
- } else if (c >= 'A' && c <= 'F') {
- a = c - 'A' + 10;
- } else {
- return {};
- }
- code = (code << 4) | a;
+static Maybe<std::string> ParseUnicodeCodepoint(const char** start,
+ const char* end) {
+ char32_t code = 0;
+ for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
+ char c = **start;
+ char32_t a;
+ if (c >= '0' && c <= '9') {
+ a = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ a = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ a = c - 'A' + 10;
+ } else {
+ return {};
}
+ code = (code << 4) | a;
+ }
- ssize_t len = utf32_to_utf8_length(&code, 1);
- if (len < 0) {
- return {};
- }
+ ssize_t len = utf32_to_utf8_length(&code, 1);
+ if (len < 0) {
+ return {};
+ }
- std::string resultUtf8;
- resultUtf8.resize(len);
- utf32_to_utf8(&code, 1, &*resultUtf8.begin(), len + 1);
- return resultUtf8;
+ std::string result_utf8;
+ result_utf8.resize(len);
+ utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1);
+ return result_utf8;
}
-StringBuilder& StringBuilder::append(const StringPiece& str) {
- if (!mError.empty()) {
- return *this;
- }
-
- // Where the new data will be appended to.
- size_t newDataIndex = mStr.size();
-
- const char* const end = str.end();
- const char* start = str.begin();
- const char* current = start;
- while (current != end) {
- if (mLastCharWasEscape) {
- switch (*current) {
- case 't':
- mStr += '\t';
- break;
- case 'n':
- mStr += '\n';
- break;
- case '#':
- mStr += '#';
- break;
- case '@':
- mStr += '@';
- break;
- case '?':
- mStr += '?';
- break;
- case '"':
- mStr += '"';
- break;
- case '\'':
- mStr += '\'';
- break;
- case '\\':
- mStr += '\\';
- break;
- case 'u': {
- current++;
- Maybe<std::string> c = parseUnicodeCodepoint(¤t, end);
- if (!c) {
- mError = "invalid unicode escape sequence";
- return *this;
- }
- mStr += c.value();
- current -= 1;
- break;
- }
-
- default:
- // Ignore.
- break;
- }
- mLastCharWasEscape = false;
- start = current + 1;
- } else if (*current == '"') {
- if (!mQuote && mTrailingSpace) {
- // We found an opening quote, and we have
- // trailing space, so we should append that
- // space now.
- if (mTrailingSpace) {
- // We had trailing whitespace, so
- // replace with a single space.
- if (!mStr.empty()) {
- mStr += ' ';
- }
- mTrailingSpace = false;
- }
- }
- mQuote = !mQuote;
- mStr.append(start, current - start);
- start = current + 1;
- } else if (*current == '\'' && !mQuote) {
- // This should be escaped.
- mError = "unescaped apostrophe";
- return *this;
- } else if (*current == '\\') {
- // This is an escape sequence, convert to the real value.
- if (!mQuote && mTrailingSpace) {
- // We had trailing whitespace, so
- // replace with a single space.
- if (!mStr.empty()) {
- mStr += ' ';
- }
- mTrailingSpace = false;
- }
- mStr.append(start, current - start);
- start = current + 1;
- mLastCharWasEscape = true;
- } else if (!mQuote) {
- // This is not quoted text, so look for whitespace.
- if (isspace(*current)) {
- // We found whitespace, see if we have seen some
- // before.
- if (!mTrailingSpace) {
- // We didn't see a previous adjacent space,
- // so mark that we did.
- mTrailingSpace = true;
- mStr.append(start, current - start);
- }
-
- // Keep skipping whitespace.
- start = current + 1;
- } else if (mTrailingSpace) {
- // We saw trailing space before, so replace all
- // that trailing space with one space.
- if (!mStr.empty()) {
- mStr += ' ';
- }
- mTrailingSpace = false;
- }
- }
- current++;
- }
- mStr.append(start, end - start);
-
- // Accumulate the added string's UTF-16 length.
- ssize_t len = utf8_to_utf16_length(
- reinterpret_cast<const uint8_t*>(mStr.data()) + newDataIndex,
- mStr.size() - newDataIndex);
- if (len < 0) {
- mError = "invalid unicode code point";
- return *this;
- }
- mUtf16Len += len;
+StringBuilder& StringBuilder::Append(const StringPiece& str) {
+ if (!error_.empty()) {
return *this;
-}
+ }
-std::u16string utf8ToUtf16(const StringPiece& utf8) {
- ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()),
- utf8.length());
- if (utf16Length <= 0) {
- return {};
- }
+ // Where the new data will be appended to.
+ size_t new_data_index = str_.size();
- std::u16string utf16;
- utf16.resize(utf16Length);
- utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(),
- &*utf16.begin(), utf16Length + 1);
- return utf16;
-}
-
-std::string utf16ToUtf8(const StringPiece16& utf16) {
- ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
- if (utf8Length <= 0) {
- return {};
- }
-
- std::string utf8;
- utf8.resize(utf8Length);
- utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8Length + 1);
- return utf8;
-}
-
-bool writeAll(std::ostream& out, const BigBuffer& buffer) {
- for (const auto& b : buffer) {
- if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
- return false;
+ const char* const end = str.end();
+ const char* start = str.begin();
+ const char* current = start;
+ while (current != end) {
+ if (last_char_was_escape_) {
+ switch (*current) {
+ case 't':
+ str_ += '\t';
+ break;
+ case 'n':
+ str_ += '\n';
+ break;
+ case '#':
+ str_ += '#';
+ break;
+ case '@':
+ str_ += '@';
+ break;
+ case '?':
+ str_ += '?';
+ break;
+ case '"':
+ str_ += '"';
+ break;
+ case '\'':
+ str_ += '\'';
+ break;
+ case '\\':
+ str_ += '\\';
+ break;
+ case 'u': {
+ current++;
+ Maybe<std::string> c = ParseUnicodeCodepoint(¤t, end);
+ if (!c) {
+ error_ = "invalid unicode escape sequence";
+ return *this;
+ }
+ str_ += c.value();
+ current -= 1;
+ break;
}
+
+ default:
+ // Ignore.
+ break;
+ }
+ last_char_was_escape_ = false;
+ start = current + 1;
+ } else if (*current == '"') {
+ if (!quote_ && trailing_space_) {
+ // We found an opening quote, and we have
+ // trailing space, so we should append that
+ // space now.
+ if (trailing_space_) {
+ // We had trailing whitespace, so
+ // replace with a single space.
+ if (!str_.empty()) {
+ str_ += ' ';
+ }
+ trailing_space_ = false;
+ }
+ }
+ quote_ = !quote_;
+ str_.append(start, current - start);
+ start = current + 1;
+ } else if (*current == '\'' && !quote_) {
+ // This should be escaped.
+ error_ = "unescaped apostrophe";
+ return *this;
+ } else if (*current == '\\') {
+ // This is an escape sequence, convert to the real value.
+ if (!quote_ && trailing_space_) {
+ // We had trailing whitespace, so
+ // replace with a single space.
+ if (!str_.empty()) {
+ str_ += ' ';
+ }
+ trailing_space_ = false;
+ }
+ str_.append(start, current - start);
+ start = current + 1;
+ last_char_was_escape_ = true;
+ } else if (!quote_) {
+ // This is not quoted text, so look for whitespace.
+ if (isspace(*current)) {
+ // We found whitespace, see if we have seen some
+ // before.
+ if (!trailing_space_) {
+ // We didn't see a previous adjacent space,
+ // so mark that we did.
+ trailing_space_ = true;
+ str_.append(start, current - start);
+ }
+
+ // Keep skipping whitespace.
+ start = current + 1;
+ } else if (trailing_space_) {
+ // We saw trailing space before, so replace all
+ // that trailing space with one space.
+ if (!str_.empty()) {
+ str_ += ' ';
+ }
+ trailing_space_ = false;
+ }
}
- return true;
+ current++;
+ }
+ str_.append(start, end - start);
+
+ // Accumulate the added string's UTF-16 length.
+ ssize_t len = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index,
+ str_.size() - new_data_index);
+ if (len < 0) {
+ error_ = "invalid unicode code point";
+ return *this;
+ }
+ utf16_len_ += len;
+ return *this;
}
-std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
- std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
- uint8_t* p = data.get();
- for (const auto& block : buffer) {
- memcpy(p, block.buffer.get(), block.size);
- p += block.size;
+std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+ ssize_t utf16_length = utf8_to_utf16_length(
+ reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
+ if (utf16_length <= 0) {
+ return {};
+ }
+
+ std::u16string utf16;
+ utf16.resize(utf16_length);
+ utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(),
+ &*utf16.begin(), utf16_length + 1);
+ return utf16;
+}
+
+std::string Utf16ToUtf8(const StringPiece16& utf16) {
+ ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
+ if (utf8_length <= 0) {
+ return {};
+ }
+
+ std::string utf8;
+ utf8.resize(utf8_length);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+ return utf8;
+}
+
+bool WriteAll(std::ostream& out, const BigBuffer& buffer) {
+ for (const auto& b : buffer) {
+ if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
+ return false;
}
- return data;
+ }
+ return true;
+}
+
+std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
+ std::unique_ptr<uint8_t[]> data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+ uint8_t* p = data.get();
+ for (const auto& block : buffer) {
+ memcpy(p, block.buffer.get(), block.size);
+ p += block.size;
+ }
+ return data;
}
typename Tokenizer::iterator& Tokenizer::iterator::operator++() {
- const char* start = mToken.end();
- const char* end = mStr.end();
- if (start == end) {
- mEnd = true;
- mToken.assign(mToken.end(), 0);
- return *this;
- }
-
- start += 1;
- const char* current = start;
- while (current != end) {
- if (*current == mSeparator) {
- mToken.assign(start, current - start);
- return *this;
- }
- ++current;
- }
- mToken.assign(start, end - start);
+ const char* start = token_.end();
+ const char* end = str_.end();
+ if (start == end) {
+ end_ = true;
+ token_.assign(token_.end(), 0);
return *this;
+ }
+
+ start += 1;
+ const char* current = start;
+ while (current != end) {
+ if (*current == separator_) {
+ token_.assign(start, current - start);
+ return *this;
+ }
+ ++current;
+ }
+ token_.assign(start, end - start);
+ return *this;
}
bool Tokenizer::iterator::operator==(const iterator& rhs) const {
- // We check equality here a bit differently.
- // We need to know that the addresses are the same.
- return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
- mEnd == rhs.mEnd;
+ // We check equality here a bit differently.
+ // We need to know that the addresses are the same.
+ return token_.begin() == rhs.token_.begin() &&
+ token_.end() == rhs.token_.end() && end_ == rhs.end_;
}
bool Tokenizer::iterator::operator!=(const iterator& rhs) const {
- return !(*this == rhs);
+ return !(*this == rhs);
}
-Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end) :
- mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
-}
+Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok,
+ bool end)
+ : str_(s), separator_(sep), token_(tok), end_(end) {}
-Tokenizer::Tokenizer(StringPiece str, char sep) :
- mBegin(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
- mEnd(str, sep, StringPiece(str.end(), 0), true) {
-}
+Tokenizer::Tokenizer(StringPiece str, char sep)
+ : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
+ end_(str, sep, StringPiece(str.end(), 0), true) {}
-bool extractResFilePathParts(const StringPiece& path, StringPiece* outPrefix,
- StringPiece* outEntry, StringPiece* outSuffix) {
- const StringPiece resPrefix("res/");
- if (!stringStartsWith(path, resPrefix)) {
- return false;
+bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
+ StringPiece* out_entry, StringPiece* out_suffix) {
+ const StringPiece res_prefix("res/");
+ if (!StartsWith(path, res_prefix)) {
+ return false;
+ }
+
+ StringPiece::const_iterator last_occurence = path.end();
+ for (auto iter = path.begin() + res_prefix.size(); iter != path.end();
+ ++iter) {
+ if (*iter == '/') {
+ last_occurence = iter;
}
+ }
- StringPiece::const_iterator lastOccurence = path.end();
- for (auto iter = path.begin() + resPrefix.size(); iter != path.end(); ++iter) {
- if (*iter == '/') {
- lastOccurence = iter;
- }
- }
+ if (last_occurence == path.end()) {
+ return false;
+ }
- if (lastOccurence == path.end()) {
- return false;
- }
-
- auto iter = std::find(lastOccurence, path.end(), '.');
- *outSuffix = StringPiece(iter, path.end() - iter);
- *outEntry = StringPiece(lastOccurence + 1, iter - lastOccurence - 1);
- *outPrefix = StringPiece(path.begin(), lastOccurence - path.begin() + 1);
- return true;
+ auto iter = std::find(last_occurence, path.end(), '.');
+ *out_suffix = StringPiece(iter, path.end() - iter);
+ *out_entry = StringPiece(last_occurence + 1, iter - last_occurence - 1);
+ *out_prefix = StringPiece(path.begin(), last_occurence - path.begin() + 1);
+ return true;
}
-StringPiece16 getString16(const android::ResStringPool& pool, size_t idx) {
- size_t len;
- const char16_t* str = pool.stringAt(idx, &len);
- if (str != nullptr) {
- return StringPiece16(str, len);
- }
- return StringPiece16();
+StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char16_t* str = pool.stringAt(idx, &len);
+ if (str != nullptr) {
+ return StringPiece16(str, len);
+ }
+ return StringPiece16();
}
-std::string getString(const android::ResStringPool& pool, size_t idx) {
- size_t len;
- const char* str = pool.string8At(idx, &len);
- if (str != nullptr) {
- return std::string(str, len);
- }
- return utf16ToUtf8(getString16(pool, idx));
+std::string GetString(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char* str = pool.string8At(idx, &len);
+ if (str != nullptr) {
+ return std::string(str, len);
+ }
+ return Utf16ToUtf8(GetString16(pool, idx));
}
-} // namespace util
-} // namespace aapt
+} // namespace util
+} // namespace aapt
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 9c88354..05e9cc5 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -17,264 +17,246 @@
#ifndef AAPT_UTIL_H
#define AAPT_UTIL_H
-#include "util/BigBuffer.h"
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
#include <functional>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
+#include "androidfw/ResourceTypes.h"
+#include "utils/ByteOrder.h"
+
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+
+#ifdef _WIN32
+// TODO(adamlesinski): remove once http://b/32447322 is resolved.
+// utils/ByteOrder.h includes winsock2.h on WIN32,
+// which will pull in the ERROR definition. This conflicts
+// with android-base/logging.h, which takes care of undefining
+// ERROR, but it gets included too early (before winsock2.h).
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
namespace aapt {
namespace util {
-std::vector<std::string> split(const StringPiece& str, char sep);
-std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep);
+std::vector<std::string> Split(const StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep);
/**
* Returns true if the string starts with prefix.
*/
-bool stringStartsWith(const StringPiece& str, const StringPiece& prefix);
+bool StartsWith(const StringPiece& str, const StringPiece& prefix);
/**
* Returns true if the string ends with suffix.
*/
-bool stringEndsWith(const StringPiece& str, const StringPiece& suffix);
+bool EndsWith(const StringPiece& str, const StringPiece& suffix);
/**
* Creates a new StringPiece16 that points to a substring
* of the original string without leading or trailing whitespace.
*/
-StringPiece trimWhitespace(const StringPiece& str);
+StringPiece TrimWhitespace(const StringPiece& str);
-StringPiece trimWhitespace(const StringPiece& str);
+StringPiece TrimWhitespace(const StringPiece& str);
/**
* 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& allowed_chars);
/**
* Tests that the string is a valid Java class name.
*/
-bool isJavaClassName(const StringPiece& str);
+bool IsJavaClassName(const StringPiece& str);
/**
* Tests that the string is a valid Java package name.
*/
-bool isJavaPackageName(const StringPiece& str);
+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
* .a.b --> package.a.b
* asdf.adsf --> asdf.adsf
*/
-Maybe<std::string> getFullyQualifiedClassName(const StringPiece& package,
- const StringPiece& className);
+Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
+ const StringPiece& class_name);
/**
- * 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;
- };
-}
-
-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;
- };
+::std::function<::std::ostream&(::std::ostream&)> Joiner(
+ const Container& container, const char* sep) {
+ using std::begin;
+ using std::end;
+ const auto begin_iter = begin(container);
+ const auto end_iter = end(container);
+ return [begin_iter, end_iter, sep](::std::ostream& out) -> ::std::ostream& {
+ for (auto iter = begin_iter; iter != end_iter; ++iter) {
+ if (iter != begin_iter) {
+ out << sep;
+ }
+ out << *iter;
+ }
+ 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);
+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);
+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);
+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& ToString() 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;
+ explicit 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 str_;
+ size_t utf16_len_ = 0;
+ bool quote_ = false;
+ bool trailing_space_ = false;
+ bool last_char_was_escape_ = false;
+ std::string error_;
};
-inline const std::string& StringBuilder::str() const {
- return mStr;
-}
+inline const std::string& StringBuilder::ToString() const { return str_; }
-inline const std::string& StringBuilder::error() const {
- return mError;
-}
+inline const std::string& StringBuilder::Error() const { return error_; }
-inline size_t StringBuilder::utf16Len() const {
- return mUtf16Len;
-}
+inline size_t StringBuilder::Utf16Len() const { return utf16_len_; }
-inline StringBuilder::operator bool() const {
- return mError.empty();
-}
+inline StringBuilder::operator bool() const { return error_.empty(); }
/**
* Converts a UTF8 string to a UTF16 string.
*/
-std::u16string utf8ToUtf16(const StringPiece& utf8);
-std::string utf16ToUtf8(const StringPiece16& utf16);
+std::u16string Utf8ToUtf16(const StringPiece& utf8);
+std::string Utf16ToUtf8(const StringPiece16& utf16);
/**
* Writes the entire BigBuffer to the output stream.
*/
-bool writeAll(std::ostream& out, const BigBuffer& buffer);
+bool WriteAll(std::ostream& out, const BigBuffer& buffer);
/*
* Copies the entire BigBuffer into a single buffer.
*/
-std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer);
+std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer);
/**
* A Tokenizer implemented as an iterable collection. It does not allocate
* 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 token_; }
+ 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 str_;
+ char separator_;
+ StringPiece token_;
+ bool end_;
+ };
- Tokenizer(StringPiece str, char sep);
+ Tokenizer(StringPiece str, char sep);
- iterator begin() {
- return mBegin;
- }
+ iterator begin() { return begin_; }
- iterator end() {
- return mEnd;
- }
+ iterator end() { return end_; }
-private:
- const iterator mBegin;
- const iterator mEnd;
+ private:
+ const iterator begin_;
+ const iterator end_;
};
-inline Tokenizer tokenize(const StringPiece& str, char sep) {
- return Tokenizer(str, sep);
+inline Tokenizer Tokenize(const StringPiece& str, char 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
@@ -285,20 +267,22 @@
*
* Returns true if successful.
*/
-bool extractResFilePathParts(const StringPiece& path, StringPiece* outPrefix,
- StringPiece* outEntry, StringPiece* outSuffix);
+bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
+ StringPiece* out_entry, StringPiece* out_suffix);
-} // 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/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 0e27213..cac3de4 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -14,191 +14,196 @@
* limitations under the License.
*/
-#include "test/Test.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
#include <string>
+#include "test/Test.h"
+
namespace aapt {
TEST(UtilTest, TrimOnlyWhitespace) {
- const std::string full = "\n ";
+ const std::string full = "\n ";
- StringPiece trimmed = util::trimWhitespace(full);
- EXPECT_TRUE(trimmed.empty());
- EXPECT_EQ(0u, trimmed.size());
+ StringPiece trimmed = util::TrimWhitespace(full);
+ EXPECT_TRUE(trimmed.empty());
+ EXPECT_EQ(0u, trimmed.size());
}
TEST(UtilTest, StringEndsWith) {
- EXPECT_TRUE(util::stringEndsWith("hello.xml", ".xml"));
+ EXPECT_TRUE(util::EndsWith("hello.xml", ".xml"));
}
TEST(UtilTest, StringStartsWith) {
- EXPECT_TRUE(util::stringStartsWith("hello.xml", "he"));
+ EXPECT_TRUE(util::StartsWith("hello.xml", "he"));
}
TEST(UtilTest, StringBuilderSplitEscapeSequence) {
- EXPECT_EQ(StringPiece("this is a new\nline."),
- util::StringBuilder().append("this is a new\\")
- .append("nline.")
- .str());
+ EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder()
+ .Append("this is a new\\")
+ .Append("nline.")
+ .ToString());
}
TEST(UtilTest, StringBuilderWhitespaceRemoval) {
- EXPECT_EQ(StringPiece("hey guys this is so cool"),
- util::StringBuilder().append(" hey guys ")
- .append(" this is so cool ")
- .str());
+ EXPECT_EQ(StringPiece("hey guys this is so cool"),
+ util::StringBuilder()
+ .Append(" hey guys ")
+ .Append(" this is so cool ")
+ .ToString());
- EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"),
- util::StringBuilder().append(" \" wow, so many \t ")
- .append("spaces. \"what? ")
- .str());
+ EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"),
+ util::StringBuilder()
+ .Append(" \" wow, so many \t ")
+ .Append("spaces. \"what? ")
+ .ToString());
- EXPECT_EQ(StringPiece("where is the pie?"),
- util::StringBuilder().append(" where \t ")
- .append(" \nis the "" pie?")
- .str());
+ EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder()
+ .Append(" where \t ")
+ .Append(" \nis the "
+ " pie?")
+ .ToString());
}
TEST(UtilTest, StringBuilderEscaping) {
- EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"),
- util::StringBuilder().append(" hey guys\\n ")
- .append(" this \\t is so\\\\ cool ")
- .str());
+ EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"),
+ util::StringBuilder()
+ .Append(" hey guys\\n ")
+ .Append(" this \\t is so\\\\ cool ")
+ .ToString());
- EXPECT_EQ(StringPiece("@?#\\\'"),
- util::StringBuilder().append("\\@\\?\\#\\\\\\'")
- .str());
+ EXPECT_EQ(StringPiece("@?#\\\'"),
+ util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString());
}
TEST(UtilTest, StringBuilderMisplacedQuote) {
- util::StringBuilder builder{};
- EXPECT_FALSE(builder.append("they're coming!"));
+ util::StringBuilder builder{};
+ EXPECT_FALSE(builder.Append("they're coming!"));
}
TEST(UtilTest, StringBuilderUnicodeCodes) {
- EXPECT_EQ(std::string("\u00AF\u0AF0 woah"),
- util::StringBuilder().append("\\u00AF\\u0AF0 woah")
- .str());
+ EXPECT_EQ(std::string("\u00AF\u0AF0 woah"),
+ util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString());
- EXPECT_FALSE(util::StringBuilder().append("\\u00 yo"));
+ EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo"));
}
TEST(UtilTest, TokenizeInput) {
- auto tokenizer = util::tokenize(StringPiece("this| is|the|end"), '|');
- auto iter = tokenizer.begin();
- ASSERT_EQ(*iter, StringPiece("this"));
- ++iter;
- ASSERT_EQ(*iter, StringPiece(" is"));
- ++iter;
- ASSERT_EQ(*iter, StringPiece("the"));
- ++iter;
- ASSERT_EQ(*iter, StringPiece("end"));
- ++iter;
- ASSERT_EQ(tokenizer.end(), iter);
+ auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece("this"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece(" is"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece("the"));
+ ++iter;
+ ASSERT_EQ(*iter, StringPiece("end"));
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
}
TEST(UtilTest, TokenizeEmptyString) {
- auto tokenizer = util::tokenize(StringPiece(""), '|');
- auto iter = tokenizer.begin();
- ASSERT_NE(tokenizer.end(), iter);
- ASSERT_EQ(StringPiece(), *iter);
- ++iter;
- ASSERT_EQ(tokenizer.end(), iter);
+ auto tokenizer = util::Tokenize(StringPiece(""), '|');
+ auto iter = tokenizer.begin();
+ ASSERT_NE(tokenizer.end(), iter);
+ ASSERT_EQ(StringPiece(), *iter);
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
}
TEST(UtilTest, TokenizeAtEnd) {
- auto tokenizer = util::tokenize(StringPiece("one."), '.');
- auto iter = tokenizer.begin();
- ASSERT_EQ(*iter, StringPiece("one"));
- ++iter;
- ASSERT_NE(iter, tokenizer.end());
- ASSERT_EQ(*iter, StringPiece());
+ auto tokenizer = util::Tokenize(StringPiece("one."), '.');
+ auto iter = tokenizer.begin();
+ ASSERT_EQ(*iter, StringPiece("one"));
+ ++iter;
+ ASSERT_NE(iter, tokenizer.end());
+ ASSERT_EQ(*iter, StringPiece());
}
TEST(UtilTest, IsJavaClassName) {
- EXPECT_TRUE(util::isJavaClassName("android.test.Class"));
- EXPECT_TRUE(util::isJavaClassName("android.test.Class$Inner"));
- EXPECT_TRUE(util::isJavaClassName("android_test.test.Class"));
- EXPECT_TRUE(util::isJavaClassName("_android_.test._Class_"));
- EXPECT_FALSE(util::isJavaClassName("android.test.$Inner"));
- EXPECT_FALSE(util::isJavaClassName("android.test.Inner$"));
- EXPECT_FALSE(util::isJavaClassName(".test.Class"));
- EXPECT_FALSE(util::isJavaClassName("android"));
+ EXPECT_TRUE(util::IsJavaClassName("android.test.Class"));
+ EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner"));
+ EXPECT_TRUE(util::IsJavaClassName("android_test.test.Class"));
+ EXPECT_TRUE(util::IsJavaClassName("_android_.test._Class_"));
+ EXPECT_FALSE(util::IsJavaClassName("android.test.$Inner"));
+ EXPECT_FALSE(util::IsJavaClassName("android.test.Inner$"));
+ EXPECT_FALSE(util::IsJavaClassName(".test.Class"));
+ EXPECT_FALSE(util::IsJavaClassName("android"));
}
TEST(UtilTest, IsJavaPackageName) {
- EXPECT_TRUE(util::isJavaPackageName("android"));
- EXPECT_TRUE(util::isJavaPackageName("android.test"));
- EXPECT_TRUE(util::isJavaPackageName("android.test_thing"));
- EXPECT_FALSE(util::isJavaPackageName("_android"));
- EXPECT_FALSE(util::isJavaPackageName("android_"));
- EXPECT_FALSE(util::isJavaPackageName("android."));
- EXPECT_FALSE(util::isJavaPackageName(".android"));
- EXPECT_FALSE(util::isJavaPackageName("android._test"));
- EXPECT_FALSE(util::isJavaPackageName(".."));
+ EXPECT_TRUE(util::IsJavaPackageName("android"));
+ EXPECT_TRUE(util::IsJavaPackageName("android.test"));
+ EXPECT_TRUE(util::IsJavaPackageName("android.test_thing"));
+ EXPECT_FALSE(util::IsJavaPackageName("_android"));
+ EXPECT_FALSE(util::IsJavaPackageName("android_"));
+ EXPECT_FALSE(util::IsJavaPackageName("android."));
+ EXPECT_FALSE(util::IsJavaPackageName(".android"));
+ EXPECT_FALSE(util::IsJavaPackageName("android._test"));
+ EXPECT_FALSE(util::IsJavaPackageName(".."));
}
TEST(UtilTest, FullyQualifiedClassName) {
- Maybe<std::string> res = util::getFullyQualifiedClassName("android", ".asdf");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), "android.asdf");
+ Maybe<std::string> res = util::GetFullyQualifiedClassName("android", ".asdf");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "android.asdf");
- res = util::getFullyQualifiedClassName("android", ".a.b");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), "android.a.b");
+ res = util::GetFullyQualifiedClassName("android", ".a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "android.a.b");
- res = util::getFullyQualifiedClassName("android", "a.b");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), "a.b");
+ res = util::GetFullyQualifiedClassName("android", "a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "a.b");
- res = util::getFullyQualifiedClassName("", "a.b");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), "a.b");
+ res = util::GetFullyQualifiedClassName("", "a.b");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "a.b");
- res = util::getFullyQualifiedClassName("android", "Class");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), "android.Class");
+ res = util::GetFullyQualifiedClassName("android", "Class");
+ AAPT_ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), "android.Class");
- res = util::getFullyQualifiedClassName("", "");
- AAPT_ASSERT_FALSE(res);
+ res = util::GetFullyQualifiedClassName("", "");
+ AAPT_ASSERT_FALSE(res);
- res = util::getFullyQualifiedClassName("android", "./Apple");
- AAPT_ASSERT_FALSE(res);
+ res = util::GetFullyQualifiedClassName("android", "./Apple");
+ AAPT_ASSERT_FALSE(res);
}
TEST(UtilTest, ExtractResourcePathComponents) {
- StringPiece prefix, entry, suffix;
- ASSERT_TRUE(util::extractResFilePathParts("res/xml-sw600dp/entry.xml", &prefix, &entry,
- &suffix));
- EXPECT_EQ(prefix, "res/xml-sw600dp/");
- EXPECT_EQ(entry, "entry");
- EXPECT_EQ(suffix, ".xml");
+ StringPiece prefix, entry, suffix;
+ ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml",
+ &prefix, &entry, &suffix));
+ EXPECT_EQ(prefix, "res/xml-sw600dp/");
+ EXPECT_EQ(entry, "entry");
+ EXPECT_EQ(suffix, ".xml");
- ASSERT_TRUE(util::extractResFilePathParts("res/xml-sw600dp/entry.9.png", &prefix, &entry,
- &suffix));
+ ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.9.png",
+ &prefix, &entry, &suffix));
- EXPECT_EQ(prefix, "res/xml-sw600dp/");
- EXPECT_EQ(entry, "entry");
- EXPECT_EQ(suffix, ".9.png");
+ EXPECT_EQ(prefix, "res/xml-sw600dp/");
+ EXPECT_EQ(entry, "entry");
+ EXPECT_EQ(suffix, ".9.png");
- EXPECT_FALSE(util::extractResFilePathParts("AndroidManifest.xml", &prefix, &entry, &suffix));
- EXPECT_FALSE(util::extractResFilePathParts("res/.xml", &prefix, &entry, &suffix));
+ EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix,
+ &entry, &suffix));
+ EXPECT_FALSE(
+ util::ExtractResFilePathParts("res/.xml", &prefix, &entry, &suffix));
- ASSERT_TRUE(util::extractResFilePathParts("res//.", &prefix, &entry, &suffix));
- EXPECT_EQ(prefix, "res//");
- EXPECT_EQ(entry, "");
- EXPECT_EQ(suffix, ".");
+ ASSERT_TRUE(
+ util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix));
+ EXPECT_EQ(prefix, "res//");
+ EXPECT_EQ(entry, "");
+ EXPECT_EQ(suffix, ".");
}
TEST(UtilTest, VerifyJavaStringFormat) {
- ASSERT_TRUE(util::verifyJavaStringFormat("%09.34f"));
- ASSERT_TRUE(util::verifyJavaStringFormat("%9$.34f %8$"));
- ASSERT_TRUE(util::verifyJavaStringFormat("%% %%"));
- ASSERT_FALSE(util::verifyJavaStringFormat("%09$f %f"));
- ASSERT_FALSE(util::verifyJavaStringFormat("%09f %08s"));
+ ASSERT_TRUE(util::VerifyJavaStringFormat("%09.34f"));
+ ASSERT_TRUE(util::VerifyJavaStringFormat("%9$.34f %8$"));
+ ASSERT_TRUE(util::VerifyJavaStringFormat("%% %%"));
+ ASSERT_FALSE(util::VerifyJavaStringFormat("%09$f %f"));
+ ASSERT_FALSE(util::VerifyJavaStringFormat("%09f %08s"));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 745079c..7580b46 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -19,93 +19,94 @@
namespace aapt {
namespace xml {
-static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
- return f(el);
+static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el,
+ SourcePathDiagnostics*) {
+ return f(el);
}
-static bool wrapperTwo(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
- SourcePathDiagnostics* diag) {
- return f(el, diag);
+static bool wrapper_two(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
+ SourcePathDiagnostics* diag) {
+ return f(el, diag);
}
-void XmlNodeAction::action(XmlNodeAction::ActionFunc f) {
- mActions.emplace_back(std::bind(wrapperOne, std::move(f),
- std::placeholders::_1,
- std::placeholders::_2));
+void XmlNodeAction::Action(XmlNodeAction::ActionFunc f) {
+ actions_.emplace_back(std::bind(
+ wrapper_one, std::move(f), std::placeholders::_1, std::placeholders::_2));
}
-void XmlNodeAction::action(XmlNodeAction::ActionFuncWithDiag f) {
- mActions.emplace_back(std::bind(wrapperTwo, std::move(f),
- std::placeholders::_1,
- std::placeholders::_2));
+void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) {
+ actions_.emplace_back(std::bind(
+ wrapper_two, std::move(f), std::placeholders::_1, std::placeholders::_2));
}
-static void printElementToDiagMessage(const Element* el, DiagMessage* msg) {
- *msg << "<";
- if (!el->namespaceUri.empty()) {
- *msg << el->namespaceUri << ":";
- }
- *msg << el->name << ">";
+static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) {
+ *msg << "<";
+ if (!el->namespace_uri.empty()) {
+ *msg << el->namespace_uri << ":";
+ }
+ *msg << el->name << ">";
}
-bool XmlNodeAction::execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag,
- Element* el) const {
- bool error = false;
- for (const ActionFuncWithDiag& action : mActions) {
- error |= !action(el, diag);
+bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy,
+ SourcePathDiagnostics* diag, Element* el) const {
+ bool error = false;
+ for (const ActionFuncWithDiag& action : actions_) {
+ error |= !action(el, diag);
+ }
+
+ for (Element* child_el : el->GetChildElements()) {
+ if (child_el->namespace_uri.empty()) {
+ std::map<std::string, XmlNodeAction>::const_iterator iter =
+ map_.find(child_el->name);
+ if (iter != map_.end()) {
+ error |= !iter->second.Execute(policy, diag, child_el);
+ continue;
+ }
}
- for (Element* childEl : el->getChildElements()) {
- if (childEl->namespaceUri.empty()) {
- std::map<std::string, XmlNodeAction>::const_iterator iter = mMap.find(childEl->name);
- if (iter != mMap.end()) {
- error |= !iter->second.execute(policy, diag, childEl);
- continue;
- }
- }
-
- if (policy == XmlActionExecutorPolicy::Whitelist) {
- DiagMessage errorMsg(childEl->lineNumber);
- errorMsg << "unknown element ";
- printElementToDiagMessage(childEl, &errorMsg);
- errorMsg << " found";
- diag->error(errorMsg);
- error = true;
- }
+ if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ DiagMessage error_msg(child_el->line_number);
+ error_msg << "unknown element ";
+ PrintElementToDiagMessage(child_el, &error_msg);
+ error_msg << " found";
+ diag->Error(error_msg);
+ error = true;
}
- return !error;
+ }
+ return !error;
}
-bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag,
- XmlResource* doc) const {
- SourcePathDiagnostics sourceDiag(doc->file.source, diag);
+bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy,
+ IDiagnostics* diag, XmlResource* doc) const {
+ SourcePathDiagnostics source_diag(doc->file.source, diag);
- Element* el = findRootElement(doc);
- if (!el) {
- if (policy == XmlActionExecutorPolicy::Whitelist) {
- sourceDiag.error(DiagMessage() << "no root XML tag found");
- return false;
- }
- return true;
- }
-
- if (el->namespaceUri.empty()) {
- std::map<std::string, XmlNodeAction>::const_iterator iter = mMap.find(el->name);
- if (iter != mMap.end()) {
- return iter->second.execute(policy, &sourceDiag, el);
- }
- }
-
- if (policy == XmlActionExecutorPolicy::Whitelist) {
- DiagMessage errorMsg(el->lineNumber);
- errorMsg << "unknown element ";
- printElementToDiagMessage(el, &errorMsg);
- errorMsg << " found";
- sourceDiag.error(errorMsg);
- return false;
+ Element* el = FindRootElement(doc);
+ if (!el) {
+ if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ source_diag.Error(DiagMessage() << "no root XML tag found");
+ return false;
}
return true;
+ }
+
+ if (el->namespace_uri.empty()) {
+ std::map<std::string, XmlNodeAction>::const_iterator iter =
+ map_.find(el->name);
+ if (iter != map_.end()) {
+ return iter->second.Execute(policy, &source_diag, el);
+ }
+ }
+
+ if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ DiagMessage error_msg(el->line_number);
+ error_msg << "unknown element ";
+ PrintElementToDiagMessage(el, &error_msg);
+ error_msg << " found";
+ source_diag.Error(error_msg);
+ return false;
+ }
+ return true;
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index cad508c..68e3563 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -17,92 +17,97 @@
#ifndef AAPT_XML_XMLPATTERN_H
#define AAPT_XML_XMLPATTERN_H
-#include "Diagnostics.h"
-#include "xml/XmlDom.h"
-
-#include <android-base/macros.h>
#include <functional>
#include <map>
#include <string>
#include <vector>
+#include "android-base/macros.h"
+
+#include "Diagnostics.h"
+#include "xml/XmlDom.h"
+
namespace aapt {
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.
+ */
+ kNone,
- /**
- * 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.
+ */
+ kWhitelist,
};
/**
- * 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 map_[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> map_;
+ std::vector<ActionFuncWithDiag> actions_;
};
/**
- * 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 map_[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> map_;
- 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/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp
index 106e856..7110c90 100644
--- a/tools/aapt2/xml/XmlActionExecutor_test.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp
@@ -14,49 +14,53 @@
* limitations under the License.
*/
-#include "test/Test.h"
#include "xml/XmlActionExecutor.h"
+#include "test/Test.h"
+
namespace aapt {
namespace xml {
TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) {
- XmlActionExecutor executor;
- XmlNodeAction& manifestAction = executor["manifest"];
- XmlNodeAction& applicationAction = manifestAction["application"];
+ XmlActionExecutor executor;
+ XmlNodeAction& manifest_action = executor["manifest"];
+ XmlNodeAction& application_action = manifest_action["application"];
- Element* manifestEl = nullptr;
- manifestAction.action([&](Element* manifest) -> bool {
- manifestEl = manifest;
- return true;
- });
+ Element* manifest_el = nullptr;
+ manifest_action.Action([&](Element* manifest) -> bool {
+ manifest_el = manifest;
+ return true;
+ });
- Element* applicationEl = nullptr;
- applicationAction.action([&](Element* application) -> bool {
- applicationEl = application;
- return true;
- });
+ Element* application_el = nullptr;
+ application_action.Action([&](Element* application) -> bool {
+ application_el = application;
+ return true;
+ });
- std::unique_ptr<XmlResource> doc = test::buildXmlDom("<manifest><application /></manifest>");
+ std::unique_ptr<XmlResource> doc =
+ test::BuildXmlDom("<manifest><application /></manifest>");
- StdErrDiagnostics diag;
- ASSERT_TRUE(executor.execute(XmlActionExecutorPolicy::None, &diag, doc.get()));
- ASSERT_NE(nullptr, manifestEl);
- EXPECT_EQ(std::string("manifest"), manifestEl->name);
+ StdErrDiagnostics diag;
+ ASSERT_TRUE(
+ executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get()));
+ ASSERT_NE(nullptr, manifest_el);
+ EXPECT_EQ(std::string("manifest"), manifest_el->name);
- ASSERT_NE(nullptr, applicationEl);
- EXPECT_EQ(std::string("application"), applicationEl->name);
+ ASSERT_NE(nullptr, application_el);
+ EXPECT_EQ(std::string("application"), application_el->name);
}
TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) {
- XmlActionExecutor executor;
- executor["manifest"]["application"];
+ XmlActionExecutor executor;
+ executor["manifest"]["application"];
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(
- "<manifest><application /><activity /></manifest>");
- StdErrDiagnostics diag;
- ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get()));
+ std::unique_ptr<XmlResource> doc =
+ test::BuildXmlDom("<manifest><application /><activity /></manifest>");
+ StdErrDiagnostics diag;
+ ASSERT_FALSE(
+ executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get()));
}
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 28de78a..567418e 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -15,475 +15,496 @@
*/
#include "XmlDom.h"
-#include "XmlPullParser.h"
-#include "util/Util.h"
+
+#include <expat.h>
#include <cassert>
-#include <expat.h>
#include <memory>
#include <stack>
#include <string>
#include <tuple>
+#include "android-base/logging.h"
+
+#include "XmlPullParser.h"
+#include "util/Util.h"
+
namespace aapt {
namespace xml {
constexpr char kXmlNamespaceSep = 1;
struct Stack {
- std::unique_ptr<xml::Node> root;
- std::stack<xml::Node*> nodeStack;
- std::string pendingComment;
+ std::unique_ptr<xml::Node> root;
+ std::stack<xml::Node*> node_stack;
+ std::string pending_comment;
};
/**
* Extracts the namespace and name of an expanded element or attribute name.
*/
-static void splitName(const char* name, std::string* outNs, std::string* outName) {
- const char* p = name;
- while (*p != 0 && *p != kXmlNamespaceSep) {
- p++;
- }
+static void SplitName(const char* name, std::string* out_ns,
+ std::string* out_name) {
+ const char* p = name;
+ while (*p != 0 && *p != kXmlNamespaceSep) {
+ p++;
+ }
- if (*p == 0) {
- outNs->clear();
- *outName = StringPiece(name).toString();
- } else {
- *outNs = StringPiece(name, (p - name)).toString();
- *outName = StringPiece(p + 1).toString();
- }
+ if (*p == 0) {
+ out_ns->clear();
+ *out_name = StringPiece(name).ToString();
+ } else {
+ *out_ns = StringPiece(name, (p - name)).ToString();
+ *out_name = StringPiece(p + 1).ToString();
+ }
}
-static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> node) {
- node->lineNumber = XML_GetCurrentLineNumber(parser);
- node->columnNumber = XML_GetCurrentColumnNumber(parser);
+static void AddToStack(Stack* stack, XML_Parser parser,
+ std::unique_ptr<Node> node) {
+ node->line_number = XML_GetCurrentLineNumber(parser);
+ node->column_number = XML_GetCurrentColumnNumber(parser);
- Node* thisNode = node.get();
- if (!stack->nodeStack.empty()) {
- stack->nodeStack.top()->addChild(std::move(node));
- } else {
- stack->root = std::move(node);
- }
+ Node* this_node = node.get();
+ if (!stack->node_stack.empty()) {
+ stack->node_stack.top()->AddChild(std::move(node));
+ } else {
+ stack->root = std::move(node);
+ }
- if (!nodeCast<Text>(thisNode)) {
- stack->nodeStack.push(thisNode);
- }
+ if (!NodeCast<Text>(this_node)) {
+ stack->node_stack.push(this_node);
+ }
}
-static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix,
+ const char* uri) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
- if (prefix) {
- ns->namespacePrefix = StringPiece(prefix).toString();
- }
+ std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
+ if (prefix) {
+ ns->namespace_prefix = StringPiece(prefix).ToString();
+ }
- if (uri) {
- ns->namespaceUri = StringPiece(uri).toString();
- }
+ if (uri) {
+ ns->namespace_uri = StringPiece(uri).ToString();
+ }
- addToStack(stack, parser, std::move(ns));
+ AddToStack(stack, parser, std::move(ns));
}
-static void XMLCALL endNamespaceHandler(void* userData, const char* prefix) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- assert(!stack->nodeStack.empty());
- stack->nodeStack.pop();
+ CHECK(!stack->node_stack.empty());
+ stack->node_stack.pop();
}
-static bool lessAttribute(const Attribute& lhs, const Attribute& rhs) {
- return std::tie(lhs.namespaceUri, lhs.name, lhs.value) <
- std::tie(rhs.namespaceUri, rhs.name, rhs.value);
+static bool less_attribute(const Attribute& lhs, const Attribute& rhs) {
+ return std::tie(lhs.namespace_uri, lhs.name, lhs.value) <
+ std::tie(rhs.namespace_uri, rhs.name, rhs.value);
}
-static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL StartElementHandler(void* user_data, const char* name,
+ const char** attrs) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- std::unique_ptr<Element> el = util::make_unique<Element>();
- splitName(name, &el->namespaceUri, &el->name);
+ std::unique_ptr<Element> el = util::make_unique<Element>();
+ SplitName(name, &el->namespace_uri, &el->name);
- while (*attrs) {
- Attribute attribute;
- splitName(*attrs++, &attribute.namespaceUri, &attribute.name);
- attribute.value = StringPiece(*attrs++).toString();
+ while (*attrs) {
+ Attribute attribute;
+ SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
+ attribute.value = StringPiece(*attrs++).ToString();
- // Insert in sorted order.
- auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute,
- lessAttribute);
- el->attributes.insert(iter, std::move(attribute));
- }
+ // Insert in sorted order.
+ auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(),
+ attribute, less_attribute);
+ el->attributes.insert(iter, std::move(attribute));
+ }
- el->comment = std::move(stack->pendingComment);
- addToStack(stack, parser, std::move(el));
+ el->comment = std::move(stack->pending_comment);
+ AddToStack(stack, parser, std::move(el));
}
-static void XMLCALL endElementHandler(void* userData, const char* name) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL EndElementHandler(void* user_data, const char* name) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- assert(!stack->nodeStack.empty());
- //stack->nodeStack.top()->comment = std::move(stack->pendingComment);
- stack->nodeStack.pop();
+ CHECK(!stack->node_stack.empty());
+ // stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+ stack->node_stack.pop();
}
-static void XMLCALL characterDataHandler(void* userData, const char* s, int len) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
+ int len) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- if (!s || len <= 0) {
+ if (!s || len <= 0) {
+ return;
+ }
+
+ // See if we can just append the text to a previous text node.
+ if (!stack->node_stack.empty()) {
+ Node* currentParent = stack->node_stack.top();
+ if (!currentParent->children.empty()) {
+ Node* last_child = currentParent->children.back().get();
+ if (Text* text = NodeCast<Text>(last_child)) {
+ text->text += StringPiece(s, len).ToString();
return;
+ }
}
+ }
- // See if we can just append the text to a previous text node.
- if (!stack->nodeStack.empty()) {
- Node* currentParent = stack->nodeStack.top();
- if (!currentParent->children.empty()) {
- Node* lastChild = currentParent->children.back().get();
- if (Text* text = nodeCast<Text>(lastChild)) {
- text->text += StringPiece(s, len).toString();
- return;
- }
- }
- }
-
- std::unique_ptr<Text> text = util::make_unique<Text>();
- text->text = StringPiece(s, len).toString();
- addToStack(stack, parser, std::move(text));
+ std::unique_ptr<Text> text = util::make_unique<Text>();
+ text->text = StringPiece(s, len).ToString();
+ AddToStack(stack, parser, std::move(text));
}
-static void XMLCALL commentDataHandler(void* userData, const char* comment) {
- XML_Parser parser = reinterpret_cast<XML_Parser>(userData);
- Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
+ XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
+ Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- if (!stack->pendingComment.empty()) {
- stack->pendingComment += '\n';
- }
- stack->pendingComment += comment;
+ if (!stack->pending_comment.empty()) {
+ stack->pending_comment += '\n';
+ }
+ stack->pending_comment += comment;
}
-std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
- Stack stack;
-
- XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
- XML_SetUserData(parser, &stack);
- XML_UseParserAsHandlerArg(parser);
- XML_SetElementHandler(parser, startElementHandler, endElementHandler);
- XML_SetNamespaceDeclHandler(parser, startNamespaceHandler, endNamespaceHandler);
- XML_SetCharacterDataHandler(parser, characterDataHandler);
- XML_SetCommentHandler(parser, commentDataHandler);
-
- char buffer[1024];
- while (!in->eof()) {
- in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
- if (in->bad() && !in->eof()) {
- stack.root = {};
- diag->error(DiagMessage(source) << strerror(errno));
- break;
- }
-
- if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
- stack.root = {};
- diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser)))
- << XML_ErrorString(XML_GetErrorCode(parser)));
- break;
- }
- }
-
- XML_ParserFree(parser);
- if (stack.root) {
- return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root));
- }
- return {};
-}
-
-static void copyAttributes(Element* el, android::ResXMLParser* parser) {
- const size_t attrCount = parser->getAttributeCount();
- if (attrCount > 0) {
- el->attributes.reserve(attrCount);
- for (size_t i = 0; i < attrCount; i++) {
- Attribute attr;
- size_t len;
- const char16_t* str16 = parser->getAttributeNamespace(i, &len);
- if (str16) {
- attr.namespaceUri = util::utf16ToUtf8(StringPiece16(str16, len));
- }
-
- str16 = parser->getAttributeName(i, &len);
- if (str16) {
- attr.name = util::utf16ToUtf8(StringPiece16(str16, len));
- }
-
- str16 = parser->getAttributeStringValue(i, &len);
- if (str16) {
- attr.value = util::utf16ToUtf8(StringPiece16(str16, len));
- }
- el->attributes.push_back(std::move(attr));
- }
- }
-}
-
-std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
+std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag,
const Source& source) {
- // 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;
+ Stack stack;
- std::unique_ptr<Node> root;
- std::stack<Node*> nodeStack;
+ XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
+ XML_SetUserData(parser, &stack);
+ XML_UseParserAsHandlerArg(parser);
+ XML_SetElementHandler(parser, StartElementHandler, EndElementHandler);
+ XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler,
+ EndNamespaceHandler);
+ XML_SetCharacterDataHandler(parser, CharacterDataHandler);
+ XML_SetCommentHandler(parser, CommentDataHandler);
- ResXMLTree tree;
- if (tree.setTo(data, dataLen) != NO_ERROR) {
- return {};
+ char buffer[1024];
+ while (!in->eof()) {
+ in->read(buffer, sizeof(buffer) / sizeof(buffer[0]));
+ if (in->bad() && !in->eof()) {
+ stack.root = {};
+ diag->Error(DiagMessage(source) << strerror(errno));
+ break;
}
- ResXMLParser::event_code_t code;
- while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
- code != ResXMLParser::END_DOCUMENT) {
- std::unique_ptr<Node> newNode;
- switch (code) {
- case ResXMLParser::START_NAMESPACE: {
- std::unique_ptr<Namespace> node = util::make_unique<Namespace>();
- size_t len;
- const char16_t* str16 = tree.getNamespacePrefix(&len);
- if (str16) {
- node->namespacePrefix = util::utf16ToUtf8(StringPiece16(str16, len));
- }
+ if (XML_Parse(parser, buffer, in->gcount(), in->eof()) ==
+ XML_STATUS_ERROR) {
+ stack.root = {};
+ diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser)))
+ << XML_ErrorString(XML_GetErrorCode(parser)));
+ break;
+ }
+ }
- str16 = tree.getNamespaceUri(&len);
- if (str16) {
- node->namespaceUri = util::utf16ToUtf8(StringPiece16(str16, len));
- }
- newNode = std::move(node);
- break;
- }
+ XML_ParserFree(parser);
+ if (stack.root) {
+ return util::make_unique<XmlResource>(ResourceFile{{}, {}, source},
+ std::move(stack.root));
+ }
+ return {};
+}
- case ResXMLParser::START_TAG: {
- std::unique_ptr<Element> node = util::make_unique<Element>();
- size_t len;
- const char16_t* str16 = tree.getElementNamespace(&len);
- if (str16) {
- node->namespaceUri = util::utf16ToUtf8(StringPiece16(str16, len));
- }
+static void CopyAttributes(Element* el, android::ResXMLParser* parser) {
+ const size_t attr_count = parser->getAttributeCount();
+ if (attr_count > 0) {
+ el->attributes.reserve(attr_count);
+ for (size_t i = 0; i < attr_count; i++) {
+ Attribute attr;
+ size_t len;
+ const char16_t* str16 = parser->getAttributeNamespace(i, &len);
+ if (str16) {
+ attr.namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
- str16 = tree.getElementName(&len);
- if (str16) {
- node->name = util::utf16ToUtf8(StringPiece16(str16, len));
- }
+ str16 = parser->getAttributeName(i, &len);
+ if (str16) {
+ attr.name = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
- copyAttributes(node.get(), &tree);
+ str16 = parser->getAttributeStringValue(i, &len);
+ if (str16) {
+ attr.value = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+ el->attributes.push_back(std::move(attr));
+ }
+ }
+}
- newNode = std::move(node);
- break;
- }
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len,
+ IDiagnostics* diag, const Source& source) {
+ // 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;
- case ResXMLParser::TEXT: {
- std::unique_ptr<Text> node = util::make_unique<Text>();
- size_t len;
- const char16_t* str16 = tree.getText(&len);
- if (str16) {
- node->text = util::utf16ToUtf8(StringPiece16(str16, len));
- }
- newNode = std::move(node);
- break;
- }
+ std::unique_ptr<Node> root;
+ std::stack<Node*> node_stack;
- case ResXMLParser::END_NAMESPACE:
- case ResXMLParser::END_TAG:
- assert(!nodeStack.empty());
- nodeStack.pop();
- break;
+ ResXMLTree tree;
+ if (tree.setTo(data, data_len) != NO_ERROR) {
+ return {};
+ }
- default:
- assert(false);
- break;
+ ResXMLParser::event_code_t code;
+ while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
+ code != ResXMLParser::END_DOCUMENT) {
+ std::unique_ptr<Node> new_node;
+ switch (code) {
+ case ResXMLParser::START_NAMESPACE: {
+ std::unique_ptr<Namespace> node = util::make_unique<Namespace>();
+ size_t len;
+ const char16_t* str16 = tree.getNamespacePrefix(&len);
+ if (str16) {
+ node->namespace_prefix = util::Utf16ToUtf8(StringPiece16(str16, len));
}
- if (newNode) {
- newNode->lineNumber = tree.getLineNumber();
-
- Node* thisNode = newNode.get();
- if (!root) {
- assert(nodeStack.empty());
- root = std::move(newNode);
- } else {
- assert(!nodeStack.empty());
- nodeStack.top()->addChild(std::move(newNode));
- }
-
- if (!nodeCast<Text>(thisNode)) {
- nodeStack.push(thisNode);
- }
+ str16 = tree.getNamespaceUri(&len);
+ if (str16) {
+ node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));
}
- }
- return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
-}
+ new_node = std::move(node);
+ break;
+ }
-std::unique_ptr<Node> Namespace::clone() {
- auto ns = util::make_unique<Namespace>();
- ns->comment = comment;
- ns->lineNumber = lineNumber;
- ns->columnNumber = columnNumber;
- ns->namespacePrefix = namespacePrefix;
- ns->namespaceUri = namespaceUri;
-
- ns->children.reserve(children.size());
- for (const std::unique_ptr<xml::Node>& child : children) {
- ns->addChild(child->clone());
- }
- return std::move(ns);
-}
-
-Element* findRootElement(XmlResource* doc) {
- return findRootElement(doc->root.get());
-}
-
-Element* findRootElement(Node* node) {
- if (!node) {
- return nullptr;
- }
-
- Element* el = nullptr;
- while ((el = nodeCast<Element>(node)) == nullptr) {
- if (node->children.empty()) {
- return nullptr;
+ case ResXMLParser::START_TAG: {
+ std::unique_ptr<Element> node = util::make_unique<Element>();
+ size_t len;
+ const char16_t* str16 = tree.getElementNamespace(&len);
+ if (str16) {
+ node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len));
}
- // We are looking for the first element, and namespaces can only have one child.
- node = node->children.front().get();
- }
- return el;
-}
-void Node::addChild(std::unique_ptr<Node> child) {
- child->parent = this;
- children.push_back(std::move(child));
-}
-
-Attribute* Element::findAttribute(const StringPiece& ns, const StringPiece& name) {
- for (auto& attr : attributes) {
- if (ns == attr.namespaceUri && name == attr.name) {
- return &attr;
+ str16 = tree.getElementName(&len);
+ if (str16) {
+ node->name = util::Utf16ToUtf8(StringPiece16(str16, len));
}
+
+ CopyAttributes(node.get(), &tree);
+
+ new_node = std::move(node);
+ break;
+ }
+
+ case ResXMLParser::TEXT: {
+ std::unique_ptr<Text> node = util::make_unique<Text>();
+ size_t len;
+ const char16_t* str16 = tree.getText(&len);
+ if (str16) {
+ node->text = util::Utf16ToUtf8(StringPiece16(str16, len));
+ }
+ new_node = std::move(node);
+ break;
+ }
+
+ case ResXMLParser::END_NAMESPACE:
+ case ResXMLParser::END_TAG:
+ CHECK(!node_stack.empty());
+ node_stack.pop();
+ break;
+
+ default:
+ LOG(FATAL) << "unhandled XML chunk type";
+ break;
}
+
+ if (new_node) {
+ new_node->line_number = tree.getLineNumber();
+
+ Node* this_node = new_node.get();
+ if (!root) {
+ CHECK(node_stack.empty()) << "node stack should be empty";
+ root = std::move(new_node);
+ } else {
+ CHECK(!node_stack.empty()) << "node stack should not be empty";
+ node_stack.top()->AddChild(std::move(new_node));
+ }
+
+ if (!NodeCast<Text>(this_node)) {
+ node_stack.push(this_node);
+ }
+ }
+ }
+ return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
+}
+
+std::unique_ptr<Node> Namespace::Clone() {
+ auto ns = util::make_unique<Namespace>();
+ ns->comment = comment;
+ ns->line_number = line_number;
+ ns->column_number = column_number;
+ ns->namespace_prefix = namespace_prefix;
+ ns->namespace_uri = namespace_uri;
+
+ ns->children.reserve(children.size());
+ for (const std::unique_ptr<xml::Node>& child : children) {
+ ns->AddChild(child->Clone());
+ }
+ return std::move(ns);
+}
+
+Element* FindRootElement(XmlResource* doc) {
+ return FindRootElement(doc->root.get());
+}
+
+Element* FindRootElement(Node* node) {
+ if (!node) {
return nullptr;
-}
+ }
-Element* Element::findChild(const StringPiece& ns, const StringPiece& name) {
- return findChildWithAttribute(ns, name, {}, {}, {});
-}
-
-Element* Element::findChildWithAttribute(const StringPiece& ns, const StringPiece& name,
- const StringPiece& attrNs, const StringPiece& attrName,
- const StringPiece& attrValue) {
- for (auto& childNode : children) {
- Node* child = childNode.get();
- while (nodeCast<Namespace>(child)) {
- if (child->children.empty()) {
- break;
- }
- child = child->children[0].get();
- }
-
- if (Element* el = nodeCast<Element>(child)) {
- if (ns == el->namespaceUri && name == el->name) {
- if (attrNs.empty() && attrName.empty()) {
- return el;
- }
-
- Attribute* attr = el->findAttribute(attrNs, attrName);
- if (attr && attrValue == attr->value) {
- return el;
- }
- }
- }
+ Element* el = nullptr;
+ while ((el = NodeCast<Element>(node)) == nullptr) {
+ if (node->children.empty()) {
+ return nullptr;
}
- return nullptr;
+ // We are looking for the first element, and namespaces can only have one
+ // child.
+ node = node->children.front().get();
+ }
+ return el;
}
-std::vector<Element*> Element::getChildElements() {
- std::vector<Element*> elements;
- for (auto& childNode : children) {
- Node* child = childNode.get();
- while (nodeCast<Namespace>(child)) {
- if (child->children.empty()) {
- break;
- }
- child = child->children[0].get();
- }
+void Node::AddChild(std::unique_ptr<Node> child) {
+ child->parent = this;
+ children.push_back(std::move(child));
+}
- if (Element* el = nodeCast<Element>(child)) {
- elements.push_back(el);
- }
+Attribute* Element::FindAttribute(const StringPiece& ns,
+ const StringPiece& name) {
+ for (auto& attr : attributes) {
+ if (ns == attr.namespace_uri && name == attr.name) {
+ return &attr;
}
- return elements;
+ }
+ return nullptr;
}
-std::unique_ptr<Node> Element::clone() {
- auto el = util::make_unique<Element>();
- el->comment = comment;
- el->lineNumber = lineNumber;
- el->columnNumber = columnNumber;
- el->name = name;
- el->namespaceUri = namespaceUri;
+Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
+ return FindChildWithAttribute(ns, name, {}, {}, {});
+}
- el->attributes.reserve(attributes.size());
- for (xml::Attribute& attr : attributes) {
- // Don't copy compiled values or attributes.
- el->attributes.push_back(xml::Attribute{ attr.namespaceUri, attr.name, attr.value });
+Element* Element::FindChildWithAttribute(const StringPiece& ns,
+ const StringPiece& name,
+ const StringPiece& attr_ns,
+ const StringPiece& attr_name,
+ const StringPiece& attr_value) {
+ for (auto& child_node : children) {
+ Node* child = child_node.get();
+ while (NodeCast<Namespace>(child)) {
+ if (child->children.empty()) {
+ break;
+ }
+ child = child->children[0].get();
}
- el->children.reserve(children.size());
- for (const std::unique_ptr<xml::Node>& child : children) {
- el->addChild(child->clone());
+ if (Element* el = NodeCast<Element>(child)) {
+ if (ns == el->namespace_uri && name == el->name) {
+ if (attr_ns.empty() && attr_name.empty()) {
+ return el;
+ }
+
+ Attribute* attr = el->FindAttribute(attr_ns, attr_name);
+ if (attr && attr_value == attr->value) {
+ return el;
+ }
+ }
}
- return std::move(el);
+ }
+ return nullptr;
}
-std::unique_ptr<Node> Text::clone() {
- auto t = util::make_unique<Text>();
- t->comment = comment;
- t->lineNumber = lineNumber;
- t->columnNumber = columnNumber;
- t->text = text;
- return std::move(t);
+std::vector<Element*> Element::GetChildElements() {
+ std::vector<Element*> elements;
+ for (auto& child_node : children) {
+ Node* child = child_node.get();
+ while (NodeCast<Namespace>(child)) {
+ if (child->children.empty()) {
+ break;
+ }
+ child = child->children[0].get();
+ }
+
+ if (Element* el = NodeCast<Element>(child)) {
+ elements.push_back(el);
+ }
+ }
+ return elements;
}
-void PackageAwareVisitor::visit(Namespace* ns) {
- bool added = false;
- if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
- ExtractedPackage& package = maybePackage.value();
- mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) });
- added = true;
- }
+std::unique_ptr<Node> Element::Clone() {
+ auto el = util::make_unique<Element>();
+ el->comment = comment;
+ el->line_number = line_number;
+ el->column_number = column_number;
+ el->name = name;
+ el->namespace_uri = namespace_uri;
- Visitor::visit(ns);
+ el->attributes.reserve(attributes.size());
+ for (xml::Attribute& attr : attributes) {
+ // Don't copy compiled values or attributes.
+ el->attributes.push_back(
+ xml::Attribute{attr.namespace_uri, attr.name, attr.value});
+ }
- if (added) {
- mPackageDecls.pop_back();
- }
+ el->children.reserve(children.size());
+ for (const std::unique_ptr<xml::Node>& child : children) {
+ el->AddChild(child->Clone());
+ }
+ return std::move(el);
}
-Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias(
- const StringPiece& alias, const StringPiece& localPackage) const {
- if (alias.empty()) {
- return ExtractedPackage{ localPackage.toString(), false /* private */ };
- }
-
- const auto rend = mPackageDecls.rend();
- for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
- if (alias == iter->prefix) {
- if (iter->package.package.empty()) {
- return ExtractedPackage{ localPackage.toString(),
- iter->package.privateNamespace };
- }
- return iter->package;
- }
- }
- return {};
+std::unique_ptr<Node> Text::Clone() {
+ auto t = util::make_unique<Text>();
+ t->comment = comment;
+ t->line_number = line_number;
+ t->column_number = column_number;
+ t->text = text;
+ return std::move(t);
}
-} // namespace xml
-} // namespace aapt
+void PackageAwareVisitor::Visit(Namespace* ns) {
+ bool added = false;
+ if (Maybe<ExtractedPackage> maybe_package =
+ ExtractPackageFromNamespace(ns->namespace_uri)) {
+ ExtractedPackage& package = maybe_package.value();
+ package_decls_.push_back(
+ PackageDecl{ns->namespace_prefix, std::move(package)});
+ added = true;
+ }
+
+ Visitor::Visit(ns);
+
+ if (added) {
+ package_decls_.pop_back();
+ }
+}
+
+Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
+ const StringPiece& alias, const StringPiece& local_package) const {
+ if (alias.empty()) {
+ return ExtractedPackage{local_package.ToString(), false /* private */};
+ }
+
+ const auto rend = package_decls_.rend();
+ for (auto iter = package_decls_.rbegin(); iter != rend; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{local_package.ToString(),
+ iter->package.private_namespace};
+ }
+ return iter->package;
+ }
+ }
+ return {};
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index e4f41b0..e771d87 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -17,6 +17,11 @@
#ifndef AAPT_XML_DOM_H
#define AAPT_XML_DOM_H
+#include <istream>
+#include <memory>
+#include <string>
+#include <vector>
+
#include "Diagnostics.h"
#include "Resource.h"
#include "ResourceValues.h"
@@ -24,11 +29,6 @@
#include "util/Util.h"
#include "xml/XmlUtil.h"
-#include <istream>
-#include <memory>
-#include <string>
-#include <vector>
-
namespace aapt {
namespace xml {
@@ -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 line_number = 0;
+ size_t column_number = 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,174 @@
*/
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 namespace_prefix;
+ std::string namespace_uri;
- 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 namespace_uri;
+ std::string name;
+ std::string value;
- Maybe<AaptAttribute> compiledAttribute;
- std::unique_ptr<Item> compiledValue;
+ Maybe<AaptAttribute> compiled_attribute;
+ std::unique_ptr<Item> compiled_value;
};
/**
* An Element XML node.
*/
class Element : public BaseNode<Element> {
-public:
- std::string namespaceUri;
- std::string name;
- std::vector<Attribute> attributes;
+ public:
+ std::string namespace_uri;
+ 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& attr_ns,
+ const StringPiece& attr_name,
+ const StringPiece& attr_value);
+ 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 data_len,
+ IDiagnostics* diag, const Source& source);
-Element* findRootElement(XmlResource* doc);
-Element* findRootElement(Node* node);
+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& local_package) 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> package_decls_;
};
// Implementations
template <typename Derived>
-void BaseNode<Derived>::accept(RawVisitor* visitor) {
- visitor->visit(static_cast<Derived*>(this));
+void BaseNode<Derived>::Accept(RawVisitor* visitor) {
+ 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;
+T* NodeCast(Node* node) {
+ 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/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 1909f75..a414afe 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -16,17 +16,19 @@
#include "xml/XmlDom.h"
-#include <gtest/gtest.h>
#include <sstream>
#include <string>
+#include "test/Test.h"
+
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(XmlDomTest, Inflate) {
- std::stringstream in(kXmlPreamble);
- in << R"EOF(
+ std::stringstream in(kXmlPreamble);
+ in << R"EOF(
<Layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -36,15 +38,15 @@
</Layout>
)EOF";
- const Source source = { "test.xml" };
- StdErrDiagnostics diag;
- std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
- ASSERT_NE(doc, nullptr);
+ const Source source = {"test.xml"};
+ StdErrDiagnostics diag;
+ std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source);
+ ASSERT_NE(doc, nullptr);
- xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
- ASSERT_NE(ns, nullptr);
- EXPECT_EQ(ns->namespaceUri, xml::kSchemaAndroid);
- EXPECT_EQ(ns->namespacePrefix, "android");
+ xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get());
+ ASSERT_NE(ns, nullptr);
+ EXPECT_EQ(ns->namespace_uri, xml::kSchemaAndroid);
+ EXPECT_EQ(ns->namespace_prefix, "android");
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 4a944f1..e59fa86 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -14,295 +14,295 @@
* limitations under the License.
*/
+#include <iostream>
+#include <string>
+
#include "util/Maybe.h"
#include "util/Util.h"
#include "xml/XmlPullParser.h"
#include "xml/XmlUtil.h"
-#include <iostream>
-#include <string>
-
namespace aapt {
namespace xml {
constexpr char kXmlNamespaceSep = 1;
-XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
- mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
- XML_SetUserData(mParser, this);
- XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
- XML_SetNamespaceDeclHandler(mParser, startNamespaceHandler, endNamespaceHandler);
- XML_SetCharacterDataHandler(mParser, characterDataHandler);
- XML_SetCommentHandler(mParser, commentDataHandler);
- mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
+XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) {
+ parser_ = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
+ XML_SetUserData(parser_, this);
+ XML_SetElementHandler(parser_, StartElementHandler, EndElementHandler);
+ XML_SetNamespaceDeclHandler(parser_, StartNamespaceHandler,
+ EndNamespaceHandler);
+ XML_SetCharacterDataHandler(parser_, CharacterDataHandler);
+ XML_SetCommentHandler(parser_, CommentDataHandler);
+ event_queue_.push(EventData{Event::kStartDocument, 0, depth_++});
}
-XmlPullParser::~XmlPullParser() {
- XML_ParserFree(mParser);
-}
+XmlPullParser::~XmlPullParser() { XML_ParserFree(parser_); }
-XmlPullParser::Event XmlPullParser::next() {
- const Event currentEvent = getEvent();
- if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
- return currentEvent;
+XmlPullParser::Event XmlPullParser::Next() {
+ const Event currentEvent = event();
+ if (currentEvent == Event::kBadDocument ||
+ currentEvent == Event::kEndDocument) {
+ return currentEvent;
+ }
+
+ event_queue_.pop();
+ while (event_queue_.empty()) {
+ in_.read(buffer_, sizeof(buffer_) / sizeof(*buffer_));
+
+ const bool done = in_.eof();
+ if (in_.bad() && !done) {
+ error_ = strerror(errno);
+ event_queue_.push(EventData{Event::kBadDocument});
+ continue;
}
- mEventQueue.pop();
- while (mEventQueue.empty()) {
- mIn.read(mBuffer, sizeof(mBuffer) / sizeof(*mBuffer));
-
- const bool done = mIn.eof();
- if (mIn.bad() && !done) {
- mLastError = strerror(errno);
- mEventQueue.push(EventData{ Event::kBadDocument });
- continue;
- }
-
- if (XML_Parse(mParser, mBuffer, mIn.gcount(), done) == XML_STATUS_ERROR) {
- mLastError = XML_ErrorString(XML_GetErrorCode(mParser));
- mEventQueue.push(EventData{ Event::kBadDocument });
- continue;
- }
-
- if (done) {
- mEventQueue.push(EventData{ Event::kEndDocument, 0, 0 });
- }
+ if (XML_Parse(parser_, buffer_, in_.gcount(), done) == XML_STATUS_ERROR) {
+ error_ = XML_ErrorString(XML_GetErrorCode(parser_));
+ event_queue_.push(EventData{Event::kBadDocument});
+ continue;
}
- Event event = getEvent();
-
- // Record namespace prefixes and package names so that we can do our own
- // handling of references that use namespace aliases.
- if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
- Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
- if (event == Event::kStartNamespace) {
- if (result) {
- mPackageAliases.emplace_back(
- PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
- }
- } else {
- if (result) {
- mPackageAliases.pop_back();
- }
- }
+ if (done) {
+ event_queue_.push(EventData{Event::kEndDocument, 0, 0});
}
+ }
- return event;
-}
+ Event next_event = event();
-XmlPullParser::Event XmlPullParser::getEvent() const {
- return mEventQueue.front().event;
-}
-
-const std::string& XmlPullParser::getLastError() const {
- return mLastError;
-}
-
-const std::string& XmlPullParser::getComment() const {
- return mEventQueue.front().data1;
-}
-
-size_t XmlPullParser::getLineNumber() const {
- return mEventQueue.front().lineNumber;
-}
-
-size_t XmlPullParser::getDepth() const {
- return mEventQueue.front().depth;
-}
-
-const std::string& XmlPullParser::getText() const {
- if (getEvent() != Event::kText) {
- return mEmpty;
+ // Record namespace prefixes and package names so that we can do our own
+ // handling of references that use namespace aliases.
+ if (next_event == Event::kStartNamespace ||
+ next_event == Event::kEndNamespace) {
+ Maybe<ExtractedPackage> result =
+ ExtractPackageFromNamespace(namespace_uri());
+ if (next_event == Event::kStartNamespace) {
+ if (result) {
+ package_aliases_.emplace_back(
+ PackageDecl{namespace_prefix(), std::move(result.value())});
+ }
+ } else {
+ if (result) {
+ package_aliases_.pop_back();
+ }
}
- return mEventQueue.front().data1;
+ }
+
+ return next_event;
}
-const std::string& XmlPullParser::getNamespacePrefix() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
- return mEmpty;
+XmlPullParser::Event XmlPullParser::event() const {
+ return event_queue_.front().event;
+}
+
+const std::string& XmlPullParser::error() const { return error_; }
+
+const std::string& XmlPullParser::comment() const {
+ return event_queue_.front().data1;
+}
+
+size_t XmlPullParser::line_number() const {
+ return event_queue_.front().line_number;
+}
+
+size_t XmlPullParser::depth() const { return event_queue_.front().depth; }
+
+const std::string& XmlPullParser::text() const {
+ if (event() != Event::kText) {
+ return empty_;
+ }
+ return event_queue_.front().data1;
+}
+
+const std::string& XmlPullParser::namespace_prefix() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartNamespace &&
+ current_event != Event::kEndNamespace) {
+ return empty_;
+ }
+ return event_queue_.front().data1;
+}
+
+const std::string& XmlPullParser::namespace_uri() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartNamespace &&
+ current_event != Event::kEndNamespace) {
+ return empty_;
+ }
+ return event_queue_.front().data2;
+}
+
+Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias(
+ const StringPiece& alias, const StringPiece& local_package) const {
+ if (alias.empty()) {
+ return ExtractedPackage{local_package.ToString(), false /* private */};
+ }
+
+ const auto end_iter = package_aliases_.rend();
+ for (auto iter = package_aliases_.rbegin(); iter != end_iter; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{local_package.ToString(),
+ iter->package.private_namespace};
+ }
+ return iter->package;
}
- return mEventQueue.front().data1;
+ }
+ return {};
}
-const std::string& XmlPullParser::getNamespaceUri() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
- return mEmpty;
- }
- return mEventQueue.front().data2;
+const std::string& XmlPullParser::element_namespace() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartElement &&
+ current_event != Event::kEndElement) {
+ return empty_;
+ }
+ return event_queue_.front().data1;
}
-Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
- const StringPiece& alias, const StringPiece& localPackage) const {
- if (alias.empty()) {
- return ExtractedPackage{ localPackage.toString(), false /* private */ };
- }
-
- const auto endIter = mPackageAliases.rend();
- for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
- if (alias == iter->prefix) {
- if (iter->package.package.empty()) {
- return ExtractedPackage{ localPackage.toString(),
- iter->package.privateNamespace };
- }
- return iter->package;
- }
- }
- return {};
+const std::string& XmlPullParser::element_name() const {
+ const Event current_event = event();
+ if (current_event != Event::kStartElement &&
+ current_event != Event::kEndElement) {
+ return empty_;
+ }
+ return event_queue_.front().data2;
}
-const std::string& XmlPullParser::getElementNamespace() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
- return mEmpty;
- }
- return mEventQueue.front().data1;
+XmlPullParser::const_iterator XmlPullParser::begin_attributes() const {
+ return event_queue_.front().attributes.begin();
}
-const std::string& XmlPullParser::getElementName() const {
- const Event currentEvent = getEvent();
- if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
- return mEmpty;
- }
- return mEventQueue.front().data2;
+XmlPullParser::const_iterator XmlPullParser::end_attributes() const {
+ return event_queue_.front().attributes.end();
}
-XmlPullParser::const_iterator XmlPullParser::beginAttributes() const {
- return mEventQueue.front().attributes.begin();
-}
-
-XmlPullParser::const_iterator XmlPullParser::endAttributes() const {
- return mEventQueue.front().attributes.end();
-}
-
-size_t XmlPullParser::getAttributeCount() const {
- if (getEvent() != Event::kStartElement) {
- return 0;
- }
- return mEventQueue.front().attributes.size();
+size_t XmlPullParser::attribute_count() const {
+ if (event() != Event::kStartElement) {
+ return 0;
+ }
+ return event_queue_.front().attributes.size();
}
/**
* Extracts the namespace and name of an expanded element or attribute name.
*/
-static void splitName(const char* name, std::string& outNs, std::string& outName) {
- const char* p = name;
- while (*p != 0 && *p != kXmlNamespaceSep) {
- p++;
+static void SplitName(const char* name, std::string& out_ns,
+ std::string& out_name) {
+ const char* p = name;
+ while (*p != 0 && *p != kXmlNamespaceSep) {
+ p++;
+ }
+
+ if (*p == 0) {
+ out_ns = std::string();
+ out_name = name;
+ } else {
+ out_ns = StringPiece(name, (p - name)).ToString();
+ out_name = p + 1;
+ }
+}
+
+void XMLCALL XmlPullParser::StartNamespaceHandler(void* user_data,
+ const char* prefix,
+ const char* uri) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+ std::string namespace_uri = uri != nullptr ? uri : std::string();
+ parser->namespace_uris_.push(namespace_uri);
+ parser->event_queue_.push(
+ EventData{Event::kStartNamespace,
+ XML_GetCurrentLineNumber(parser->parser_), parser->depth_++,
+ prefix != nullptr ? prefix : std::string(), namespace_uri});
+}
+
+void XMLCALL XmlPullParser::StartElementHandler(void* user_data,
+ const char* name,
+ const char** attrs) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ EventData data = {Event::kStartElement,
+ XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_++};
+ SplitName(name, data.data1, data.data2);
+
+ while (*attrs) {
+ Attribute attribute;
+ SplitName(*attrs++, attribute.namespace_uri, attribute.name);
+ attribute.value = *attrs++;
+
+ // Insert in sorted order.
+ auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(),
+ attribute);
+ data.attributes.insert(iter, std::move(attribute));
+ }
+
+ // Move the structure into the queue (no copy).
+ parser->event_queue_.push(std::move(data));
+}
+
+void XMLCALL XmlPullParser::CharacterDataHandler(void* user_data, const char* s,
+ int len) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ parser->event_queue_.push(
+ EventData{Event::kText, XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_, StringPiece(s, len).ToString()});
+}
+
+void XMLCALL XmlPullParser::EndElementHandler(void* user_data,
+ const char* name) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ EventData data = {Event::kEndElement,
+ XML_GetCurrentLineNumber(parser->parser_),
+ --(parser->depth_)};
+ SplitName(name, data.data1, data.data2);
+
+ // Move the data into the queue (no copy).
+ parser->event_queue_.push(std::move(data));
+}
+
+void XMLCALL XmlPullParser::EndNamespaceHandler(void* user_data,
+ const char* prefix) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ parser->event_queue_.push(
+ EventData{Event::kEndNamespace, XML_GetCurrentLineNumber(parser->parser_),
+ --(parser->depth_), prefix != nullptr ? prefix : std::string(),
+ parser->namespace_uris_.top()});
+ parser->namespace_uris_.pop();
+}
+
+void XMLCALL XmlPullParser::CommentDataHandler(void* user_data,
+ const char* comment) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ parser->event_queue_.push(EventData{Event::kComment,
+ XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_, comment});
+}
+
+Maybe<StringPiece> FindAttribute(const XmlPullParser* parser,
+ const StringPiece& name) {
+ auto iter = parser->FindAttribute("", name);
+ if (iter != parser->end_attributes()) {
+ return StringPiece(util::TrimWhitespace(iter->value));
+ }
+ return {};
+}
+
+Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
+ const StringPiece& name) {
+ auto iter = parser->FindAttribute("", name);
+ if (iter != parser->end_attributes()) {
+ StringPiece trimmed = util::TrimWhitespace(iter->value);
+ if (!trimmed.empty()) {
+ return trimmed;
}
-
- if (*p == 0) {
- outNs = std::string();
- outName = name;
- } else {
- outNs = StringPiece(name, (p - name)).toString();
- outName = p + 1;
- }
+ }
+ return {};
}
-void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
- const char* uri) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
- std::string namespaceUri = uri != nullptr ? uri : std::string();
- parser->mNamespaceUris.push(namespaceUri);
- parser->mEventQueue.push(EventData{
- Event::kStartNamespace,
- XML_GetCurrentLineNumber(parser->mParser),
- parser->mDepth++,
- prefix != nullptr ? prefix : std::string(),
- namespaceUri
- });
-}
-
-void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name,
- const char** attrs) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- EventData data = {
- Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
- };
- splitName(name, data.data1, data.data2);
-
- while (*attrs) {
- Attribute attribute;
- splitName(*attrs++, attribute.namespaceUri, attribute.name);
- attribute.value = *attrs++;
-
- // Insert in sorted order.
- auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), attribute);
- data.attributes.insert(iter, std::move(attribute));
- }
-
- // Move the structure into the queue (no copy).
- parser->mEventQueue.push(std::move(data));
-}
-
-void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- parser->mEventQueue.push(EventData{
- Event::kText,
- XML_GetCurrentLineNumber(parser->mParser),
- parser->mDepth,
- StringPiece(s, len).toString()
- });
-}
-
-void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- EventData data = {
- Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
- };
- splitName(name, data.data1, data.data2);
-
- // Move the data into the queue (no copy).
- parser->mEventQueue.push(std::move(data));
-}
-
-void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- parser->mEventQueue.push(EventData{
- Event::kEndNamespace,
- XML_GetCurrentLineNumber(parser->mParser),
- --(parser->mDepth),
- prefix != nullptr ? prefix : std::string(),
- parser->mNamespaceUris.top()
- });
- parser->mNamespaceUris.pop();
-}
-
-void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) {
- XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData);
-
- parser->mEventQueue.push(EventData{
- Event::kComment,
- XML_GetCurrentLineNumber(parser->mParser),
- parser->mDepth,
- comment
- });
-}
-
-Maybe<StringPiece> findAttribute(const XmlPullParser* parser, const StringPiece& name) {
- auto iter = parser->findAttribute("", name);
- if (iter != parser->endAttributes()) {
- return StringPiece(util::trimWhitespace(iter->value));
- }
- return {};
-}
-
-Maybe<StringPiece> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece& name) {
- auto iter = parser->findAttribute("", name);
- if (iter != parser->endAttributes()) {
- StringPiece trimmed = util::trimWhitespace(iter->value);
- if (!trimmed.empty()) {
- return trimmed;
- }
- }
- return {};
-}
-
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index a24d109..ff58d60 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,14 +17,9 @@
#ifndef AAPT_XML_PULL_PARSER_H
#define AAPT_XML_PULL_PARSER_H
-#include "Resource.h"
-#include "process/IResourceTableConsumer.h"
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
-#include "xml/XmlUtil.h"
+#include <expat.h>
#include <algorithm>
-#include <expat.h>
#include <istream>
#include <ostream>
#include <queue>
@@ -32,267 +27,303 @@
#include <string>
#include <vector>
+#include "android-base/macros.h"
+
+#include "Resource.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
+
namespace aapt {
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 start_depth,
+ * skipping namespace nodes.
+ *
+ * When NextChildNode() returns true, you can expect Comments, Text, and
+ * StartElement events.
+ */
+ static bool NextChildNode(XmlPullParser* parser, size_t start_depth);
+ 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 event() const;
- const std::string& getLastError() const;
+ const std::string& error() 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& comment() const;
+ size_t line_number() const;
+ size_t depth() 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& text() 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& namespace_prefix() const;
+ const std::string& namespace_uri() 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& element_namespace() const;
+ const std::string& element_name() 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& local_package) 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 namespace_uri;
+ 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 begin_attributes() const;
+ const_iterator end_attributes() const;
+ size_t attribute_count() const;
+ const_iterator FindAttribute(StringPiece namespace_uri,
+ 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:
+ DISALLOW_COPY_AND_ASSIGN(XmlPullParser);
- struct EventData {
- Event event;
- size_t lineNumber;
- size_t depth;
- std::string data1;
- std::string data2;
- std::vector<Attribute> attributes;
- };
+ static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix,
+ const char* uri);
+ static void XMLCALL StartElementHandler(void* user_data, const char* name,
+ const char** attrs);
+ static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
+ int len);
+ static void XMLCALL EndElementHandler(void* user_data, const char* name);
+ static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix);
+ static void XMLCALL CommentDataHandler(void* user_data, const char* comment);
- 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 EventData {
+ Event event;
+ size_t line_number;
+ size_t depth;
+ std::string data1;
+ std::string data2;
+ std::vector<Attribute> attributes;
+ };
- struct PackageDecl {
- std::string prefix;
- ExtractedPackage package;
- };
- std::vector<PackageDecl> mPackageAliases;
+ std::istream& in_;
+ XML_Parser parser_;
+ char buffer_[16384];
+ std::queue<EventData> event_queue_;
+ std::string error_;
+ const std::string empty_;
+ size_t depth_;
+ std::stack<std::string> namespace_uris_;
+
+ struct PackageDecl {
+ std::string prefix;
+ ExtractedPackage package;
+ };
+ std::vector<PackageDecl> package_aliases_;
};
/**
* 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) {
+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 start_depth) {
+ Event event;
+
+ // First get back to the start depth.
+ while (IsGoodEvent(event = parser->Next()) &&
+ parser->depth() > start_depth + 1) {
+ }
+
+ // Now look for the first good node.
+ while ((event != Event::kEndElement || parser->depth() > start_depth) &&
+ IsGoodEvent(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";
+ case Event::kText:
+ case Event::kComment:
+ case Event::kStartElement:
+ return true;
+ default:
+ break;
}
- return out;
+ event = parser->Next();
+ }
+ return false;
}
-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) {}
-
- // 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();
+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;
}
- return false;
+ }
+ return true;
}
-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;
- }
- }
- return true;
-}
-
-inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
- return event != Event::kBadDocument && event != Event::kEndDocument;
+inline bool XmlPullParser::IsGoodEvent(XmlPullParser::Event event) {
+ 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 = namespace_uri.compare(rhs.namespace_uri);
+ 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 namespace_uri, StringPiece name) const {
+ const auto end_iter = end_attributes();
+ const auto iter = std::lower_bound(
+ begin_attributes(), end_iter,
+ std::pair<StringPiece, StringPiece>(namespace_uri, name),
+ [](const Attribute& attr,
+ const std::pair<StringPiece, StringPiece>& rhs) -> bool {
+ int cmp = attr.namespace_uri.compare(
+ 0, attr.namespace_uri.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 != end_iter && namespace_uri == iter->namespace_uri &&
+ name == iter->name) {
+ return iter;
+ }
+ return end_iter;
}
-} // 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/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 2c1fdc7..4f18cd2 100644
--- a/tools/aapt2/xml/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -14,42 +14,43 @@
* limitations under the License.
*/
-#include "test/Test.h"
-#include "util/StringPiece.h"
#include "xml/XmlPullParser.h"
#include <sstream>
+#include "test/Test.h"
+#include "util/StringPiece.h"
+
namespace aapt {
TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
- std::stringstream str;
- str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
- xml::XmlPullParser parser(str);
+ std::stringstream str;
+ str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
+ xml::XmlPullParser parser(str);
- const size_t depthOuter = parser.getDepth();
- ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+ const size_t depth_outer = parser.depth();
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(StringPiece("a"), StringPiece(parser.getElementName()));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name()));
- const size_t depthA = parser.getDepth();
- ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(StringPiece("b"), StringPiece(parser.getElementName()));
+ const size_t depth_a = parser.depth();
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name()));
- const size_t depthB = parser.getDepth();
- ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(StringPiece("c"), StringPiece(parser.getElementName()));
+ const size_t depth_b = parser.depth();
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name()));
- ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
- EXPECT_EQ(StringPiece("e"), StringPiece(parser.getElementName()));
+ ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
+ EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name()));
- ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
- EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
+ ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
+ EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event());
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index b570fd7..d00f7f2 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -14,60 +14,69 @@
* limitations under the License.
*/
-#include "util/Maybe.h"
-#include "util/Util.h"
#include "xml/XmlUtil.h"
#include <string>
+#include "util/Maybe.h"
+#include "util/Util.h"
+
namespace aapt {
namespace xml {
-std::string buildPackageNamespace(const StringPiece& package, bool privateReference) {
- std::string result = privateReference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
- result.append(package.data(), package.size());
- return result;
+std::string BuildPackageNamespace(const StringPiece& package,
+ bool private_reference) {
+ std::string result =
+ private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
+ result.append(package.data(), package.size());
+ return result;
}
-Maybe<ExtractedPackage> extractPackageFromNamespace(const std::string& namespaceUri) {
- if (util::stringStartsWith(namespaceUri, kSchemaPublicPrefix)) {
- StringPiece schemaPrefix = kSchemaPublicPrefix;
- StringPiece package = namespaceUri;
- package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
- if (package.empty()) {
- return {};
- }
- return ExtractedPackage{ package.toString(), false /* isPrivate */ };
-
- } else if (util::stringStartsWith(namespaceUri, kSchemaPrivatePrefix)) {
- StringPiece schemaPrefix = kSchemaPrivatePrefix;
- StringPiece package = namespaceUri;
- package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
- if (package.empty()) {
- return {};
- }
- return ExtractedPackage{ package.toString(), true /* isPrivate */ };
-
- } else if (namespaceUri == kSchemaAuto) {
- return ExtractedPackage{ std::string(), true /* isPrivate */ };
+Maybe<ExtractedPackage> ExtractPackageFromNamespace(
+ const std::string& namespace_uri) {
+ if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) {
+ StringPiece schema_prefix = kSchemaPublicPrefix;
+ StringPiece package = namespace_uri;
+ package = package.substr(schema_prefix.size(),
+ package.size() - schema_prefix.size());
+ if (package.empty()) {
+ return {};
}
- return {};
-}
+ return ExtractedPackage{package.ToString(), false /* is_private */};
-void transformReferenceFromNamespace(IPackageDeclStack* declStack,
- const StringPiece& localPackage, Reference* inRef) {
- if (inRef->name) {
- if (Maybe<ExtractedPackage> transformedPackage =
- declStack->transformPackageAlias(inRef->name.value().package, localPackage)) {
- ExtractedPackage& extractedPackage = transformedPackage.value();
- inRef->name.value().package = std::move(extractedPackage.package);
-
- // If the reference was already private (with a * prefix) and the namespace is public,
- // we keep the reference private.
- inRef->privateReference |= extractedPackage.privateNamespace;
- }
+ } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
+ StringPiece schema_prefix = kSchemaPrivatePrefix;
+ StringPiece package = namespace_uri;
+ package = package.substr(schema_prefix.size(),
+ package.size() - schema_prefix.size());
+ if (package.empty()) {
+ return {};
}
+ return ExtractedPackage{package.ToString(), true /* is_private */};
+
+ } else if (namespace_uri == kSchemaAuto) {
+ return ExtractedPackage{std::string(), true /* is_private */};
+ }
+ return {};
}
-} // namespace xml
-} // namespace aapt
+void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
+ const StringPiece& local_package,
+ Reference* in_ref) {
+ if (in_ref->name) {
+ if (Maybe<ExtractedPackage> transformed_package =
+ decl_stack->TransformPackageAlias(in_ref->name.value().package,
+ local_package)) {
+ ExtractedPackage& extracted_package = transformed_package.value();
+ in_ref->name.value().package = std::move(extracted_package.package);
+
+ // If the reference was already private (with a * prefix) and the
+ // namespace is public,
+ // we keep the reference private.
+ in_ref->private_reference |= extracted_package.private_namespace;
+ }
+ }
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index a6ad79d..5365401 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -17,18 +17,21 @@
#ifndef AAPT_XML_XMLUTIL_H
#define AAPT_XML_XMLUTIL_H
+#include <string>
+
#include "ResourceValues.h"
#include "util/Maybe.h"
-#include <string>
-
namespace aapt {
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 private_namespace;
};
/**
@@ -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& namespace_uri);
/**
* Returns an XML Android namespace for the given package of the form:
@@ -68,31 +74,36 @@
*
* http://schemas.android.com/apk/prv/res/<package>
*/
-std::string buildPackageNamespace(const StringPiece& package, bool privateReference=false);
+std::string BuildPackageNamespace(const StringPiece& package,
+ bool private_reference = 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& local_package) 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
- * the package declaration was private.
+ * 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);
+void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
+ const StringPiece& local_package,
+ Reference* in_ref);
-} // namespace xml
-} // namespace aapt
+} // namespace xml
+} // namespace aapt
#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
index cbeb8bc..5eecc8f 100644
--- a/tools/aapt2/xml/XmlUtil_test.cpp
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -14,38 +14,46 @@
* limitations under the License.
*/
-#include "test/Test.h"
#include "xml/XmlUtil.h"
+#include "test/Test.h"
+
namespace aapt {
TEST(XmlUtilTest, ExtractPackageFromNamespace) {
- AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace("com.android"));
- AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace("http://schemas.android.com/apk"));
- AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace("http://schemas.android.com/apk/res"));
- AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace("http://schemas.android.com/apk/res/"));
- AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace("http://schemas.android.com/apk/prv/res/"));
+ AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android"));
+ AAPT_ASSERT_FALSE(
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk"));
+ AAPT_ASSERT_FALSE(
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res"));
+ AAPT_ASSERT_FALSE(
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/"));
+ AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/prv/res/"));
- Maybe<xml::ExtractedPackage> p =
- xml::extractPackageFromNamespace("http://schemas.android.com/apk/res/a");
- AAPT_ASSERT_TRUE(p);
- EXPECT_EQ(std::string("a"), p.value().package);
- EXPECT_FALSE(p.value().privateNamespace);
+ Maybe<xml::ExtractedPackage> p =
+ xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/a");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string("a"), p.value().package);
+ EXPECT_FALSE(p.value().private_namespace);
- p = xml::extractPackageFromNamespace("http://schemas.android.com/apk/prv/res/android");
- AAPT_ASSERT_TRUE(p);
- EXPECT_EQ(std::string("android"), p.value().package);
- EXPECT_TRUE(p.value().privateNamespace);
+ p = xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/prv/res/android");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string("android"), p.value().package);
+ EXPECT_TRUE(p.value().private_namespace);
- p = xml::extractPackageFromNamespace("http://schemas.android.com/apk/prv/res/com.test");
- AAPT_ASSERT_TRUE(p);
- EXPECT_EQ(std::string("com.test"), p.value().package);
- EXPECT_TRUE(p.value().privateNamespace);
+ p = xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/prv/res/com.test");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string("com.test"), p.value().package);
+ EXPECT_TRUE(p.value().private_namespace);
- p = xml::extractPackageFromNamespace("http://schemas.android.com/apk/res-auto");
- AAPT_ASSERT_TRUE(p);
- EXPECT_EQ(std::string(), p.value().package);
- EXPECT_TRUE(p.value().privateNamespace);
+ p = xml::ExtractPackageFromNamespace(
+ "http://schemas.android.com/apk/res-auto");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::string(), p.value().package);
+ EXPECT_TRUE(p.value().private_namespace);
}
-} // namespace aapt
+} // namespace aapt
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/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index f1da3a2..e1fc5ec 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -92,8 +92,7 @@
@Nullable
public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) {
- // refSkPixelRef is a hack to get the native pointer: see #nativeRefPixelRef()
- return bitmap == null ? null : getDelegate(bitmap.refSkPixelRef());
+ return bitmap == null ? null : getDelegate(bitmap.getNativeInstance());
}
/**
@@ -601,14 +600,6 @@
return Arrays.equals(argb1, argb2);
}
- // Only used by AssetAtlasService, which we don't care about.
- @LayoutlibDelegate
- /*package*/ static long nativeRefPixelRef(long nativeBitmap) {
- // Hack: This is called by Bitmap.refSkPixelRef() and LayoutLib uses that method to get
- // the native pointer from a Bitmap. So, we return nativeBitmap here.
- return nativeBitmap;
- }
-
// ---- Private delegate/helper methods ----
private Bitmap_Delegate(BufferedImage image, Config config) {
@@ -627,7 +618,7 @@
boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
// and create/return a new Bitmap with it
- return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable,
+ return new Bitmap(nativeInt, width, height, density, isMutable,
isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index f4f92ec..db5b119 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;
@@ -604,7 +605,7 @@
}
@Override
- public void getStableInsets(Rect outInsets) throws RemoteException {
+ public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
}
@Override
@@ -612,10 +613,13 @@
throws RemoteException {}
@Override
- public void createWallpaperInputConsumer(InputChannel inputChannel) throws RemoteException {}
+ public void createInputConsumer(String name, InputChannel inputChannel)
+ throws RemoteException {}
@Override
- public void removeWallpaperInputConsumer() throws RemoteException {}
+ public boolean destroyInputConsumer(String name) throws RemoteException {
+ return false;
+ }
@Override
public Bitmap screenshotWallpaper() throws RemoteException {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 8d93b7f..a0b2977 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -76,6 +76,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -1180,7 +1181,7 @@
@Override
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) {
+ String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) {
}
};
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index feed045..91a783a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -358,7 +358,8 @@
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
mMeasuredScreenHeight, MeasureSpec.EXACTLY);
mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
- mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ mSystemViewInfoList =
+ visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
false);
return SUCCESS.createResult();
@@ -521,7 +522,8 @@
mMeasuredScreenHeight);
}
- mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+ mSystemViewInfoList =
+ visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
false);
// success!
@@ -1242,20 +1244,22 @@
* bounds of all the views.
*
* @param view the root View
- * @param offset an offset for the view bounds.
+ * @param hOffset horizontal offset for the view bounds.
+ * @param vOffset vertical offset for the view bounds.
* @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
* @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
* content frame.
*
* @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
*/
- private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
+ private ViewInfo visit(View view, int hOffset, int vOffset, boolean setExtendedInfo,
boolean isContentFrame) {
- ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
+ ViewInfo result = createViewInfo(view, hOffset, vOffset, setExtendedInfo, isContentFrame);
if (view instanceof ViewGroup) {
ViewGroup group = ((ViewGroup) view);
- result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
+ result.setChildren(visitAllChildren(group, isContentFrame ? 0 : hOffset,
+ isContentFrame ? 0 : vOffset,
setExtendedInfo, isContentFrame));
}
return result;
@@ -1267,20 +1271,22 @@
* the children of the {@code mContentRoot}.
*
* @param viewGroup the root View
- * @param offset an offset from the top for the content view frame.
+ * @param hOffset horizontal offset from the top for the content view frame.
+ * @param vOffset vertical offset from the top for the content view frame.
* @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
* @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
* content frame. {@code false} if the {@code ViewInfo} to be created is
* part of the system decor.
*/
- private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset,
boolean setExtendedInfo, boolean isContentFrame) {
if (viewGroup == null) {
return null;
}
if (!isContentFrame) {
- offset += viewGroup.getTop();
+ vOffset += viewGroup.getTop();
+ hOffset += viewGroup.getLeft();
}
int childCount = viewGroup.getChildCount();
@@ -1288,7 +1294,8 @@
List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
for (int i = 0; i < childCount; i++) {
- ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
+ ViewInfo[] childViewInfo =
+ visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset,
setExtendedInfo);
childrenWithoutOffset.add(childViewInfo[0]);
childrenWithOffset.add(childViewInfo[1]);
@@ -1298,7 +1305,7 @@
} else {
List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
for (int i = 0; i < childCount; i++) {
- children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
+ children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo,
isContentFrame));
}
return children;
@@ -1317,16 +1324,18 @@
* index 1 is with the offset.
*/
@NonNull
- private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
+ private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset,
+ boolean setExtendedInfo) {
ViewInfo[] result = new ViewInfo[2];
if (view == null) {
return result;
}
- result[0] = createViewInfo(view, 0, setExtendedInfo, true);
- result[1] = createViewInfo(view, offset, setExtendedInfo, true);
+ result[0] = createViewInfo(view, 0, 0, setExtendedInfo, true);
+ result[1] = createViewInfo(view, hOffset, vOffset, setExtendedInfo, true);
if (view instanceof ViewGroup) {
- List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
+ List<ViewInfo> children =
+ visitAllChildren((ViewGroup) view, 0, 0, setExtendedInfo, true);
result[0].setChildren(children);
result[1].setChildren(children);
}
@@ -1337,9 +1346,12 @@
* Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
* of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
* set.
- * @param offset an offset for the view bounds. Used only if view is part of the content frame.
+ * @param hOffset horizontal offset for the view bounds. Used only if view is part of the
+ * content frame.
+ * @param vOffset vertial an offset for the view bounds. Used only if view is part of the
+ * content frame.
*/
- private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
+ private ViewInfo createViewInfo(View view, int hOffset, int vOffset, boolean setExtendedInfo,
boolean isContentFrame) {
if (view == null) {
return null;
@@ -1355,9 +1367,9 @@
// The view is part of the layout added by the user. Hence,
// the ViewCookie may be obtained only through the Context.
result = new ViewInfo(view.getClass().getName(),
- getContext().getViewKey(view),
- -scrollX + view.getLeft(), -scrollY + view.getTop() + offset,
- -scrollX + view.getRight(), -scrollY + view.getBottom() + offset,
+ getContext().getViewKey(view), -scrollX + view.getLeft() + hOffset,
+ -scrollY + view.getTop() + vOffset, -scrollX + view.getRight() + hOffset,
+ -scrollY + view.getBottom() + vOffset,
view, view.getLayoutParams());
} else {
// We are part of the system decor.
diff --git a/tools/streaming_proto/Android.mk b/tools/streaming_proto/Android.mk
new file mode 100644
index 0000000..5a54fd1
--- /dev/null
+++ b/tools/streaming_proto/Android.mk
@@ -0,0 +1,41 @@
+#
+# 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 := protoc-gen-javastream
+LOCAL_SRC_FILES := \
+ Errors.cpp \
+ string_utils.cpp \
+ main.cpp
+LOCAL_SHARED_LIBRARIES := \
+ libprotoc
+include $(BUILD_HOST_EXECUTABLE)
+
+# ==========================================================
+# Build the java test
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, test) \
+ $(call all-proto-files-under, test)
+LOCAL_MODULE := StreamingProtoTest
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
+include $(BUILD_JAVA_LIBRARY)
+
diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp
new file mode 100644
index 0000000..91c6b92
--- /dev/null
+++ b/tools/streaming_proto/Errors.cpp
@@ -0,0 +1,87 @@
+#include "Errors.h"
+
+#include <stdlib.h>
+
+namespace android {
+namespace javastream_proto {
+
+Errors ERRORS;
+
+const string UNKNOWN_FILE;
+const int UNKNOWN_LINE = 0;
+
+Error::Error()
+{
+}
+
+Error::Error(const Error& that)
+ :filename(that.filename),
+ lineno(that.lineno),
+ message(that.message)
+{
+}
+
+Error::Error(const string& f, int l, const char* m)
+ :filename(f),
+ lineno(l),
+ message(m)
+{
+}
+
+Errors::Errors()
+ :m_errors()
+{
+}
+
+Errors::~Errors()
+{
+}
+
+void
+Errors::Add(const string& filename, int lineno, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ AddImpl(filename, lineno, format, args);
+ va_end(args);
+}
+
+void
+Errors::AddImpl(const string& filename, int lineno, const char* format, va_list args)
+{
+ va_list args2;
+ va_copy(args2, args);
+ int message_size = vsnprintf((char*)NULL, 0, format, args2);
+ va_end(args2);
+
+ char* buffer = new char[message_size+1];
+ vsnprintf(buffer, message_size, format, args);
+ Error error(filename, lineno, buffer);
+ delete[] buffer;
+
+ m_errors.push_back(error);
+}
+
+void
+Errors::Print() const
+{
+ for (vector<Error>::const_iterator it = m_errors.begin(); it != m_errors.end(); it++) {
+ if (it->filename == UNKNOWN_FILE) {
+ fprintf(stderr, "%s", it->message.c_str());
+ } else if (it->lineno == UNKNOWN_LINE) {
+ fprintf(stderr, "%s:%s", it->filename.c_str(), it->message.c_str());
+ } else {
+ fprintf(stderr, "%s:%d:%s", it->filename.c_str(), it->lineno, it->message.c_str());
+ }
+ }
+}
+
+bool
+Errors::HasErrors() const
+{
+ return m_errors.size() > 0;
+}
+
+} // namespace javastream_proto
+} // namespace android
+
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
new file mode 100644
index 0000000..109195a
--- /dev/null
+++ b/tools/streaming_proto/Errors.h
@@ -0,0 +1,48 @@
+#include <stdio.h>
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+struct Error
+{
+ Error();
+ explicit Error(const Error& that);
+ Error(const string& filename, int lineno, const char* message);
+
+ string filename;
+ int lineno;
+ string message;
+};
+
+class Errors
+{
+public:
+ Errors();
+ ~Errors();
+
+ // Add an error
+ void Add(const string& filename, int lineno, const char* format, ...);
+
+ // Print the errors to stderr if there are any.
+ void Print() const;
+
+ bool HasErrors() const;
+
+private:
+ // The errors that have been added
+ vector<Error> m_errors;
+ void AddImpl(const string& filename, int lineno, const char* format, va_list ap);
+};
+
+extern Errors ERRORS;
+extern const string UNKNOWN_FILE;
+extern const int UNKNOWN_LINE;
+
+
+} // namespace javastream_proto
+} // namespace android
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
new file mode 100644
index 0000000..5435728
--- /dev/null
+++ b/tools/streaming_proto/main.cpp
@@ -0,0 +1,408 @@
+#include "Errors.h"
+
+#include "string_utils.h"
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
+
+#include <stdio.h>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <map>
+
+using namespace android::javastream_proto;
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace google::protobuf::io;
+using namespace std;
+
+const int FIELD_TYPE_SHIFT = 32;
+const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+
+const int FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+static bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+ const int N = request.file_to_generate_size();
+ for (int i=0; i<N; i++) {
+ if (request.file_to_generate(i) == file) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string
+make_outer_class_name(const FileDescriptorProto& file_descriptor)
+{
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string
+make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string
+make_file_name(const FileDescriptorProto& file_descriptor)
+{
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += make_outer_class_name(file_descriptor);
+ result += ".java";
+
+ return result;
+}
+
+static string
+indent_more(const string& indent)
+{
+ return indent + " ";
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void
+write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
+{
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i=0; i<N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int "
+ << make_constant_name(value.name())
+ << " = " << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Get the string name for a field.
+ */
+static string
+get_proto_type(const FieldDescriptorProto& field)
+{
+ switch (field.type()) {
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ return "double";
+ case FieldDescriptorProto::TYPE_FLOAT:
+ return "float";
+ case FieldDescriptorProto::TYPE_INT64:
+ return "int64";
+ case FieldDescriptorProto::TYPE_UINT64:
+ return "uint64";
+ case FieldDescriptorProto::TYPE_INT32:
+ return "int32";
+ case FieldDescriptorProto::TYPE_FIXED64:
+ return "fixed64";
+ case FieldDescriptorProto::TYPE_FIXED32:
+ return "fixed32";
+ case FieldDescriptorProto::TYPE_BOOL:
+ return "bool";
+ case FieldDescriptorProto::TYPE_STRING:
+ return "string";
+ case FieldDescriptorProto::TYPE_GROUP:
+ return "group<unsupported!>";
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_BYTES:
+ return "bytes";
+ case FieldDescriptorProto::TYPE_UINT32:
+ return "uint32";
+ case FieldDescriptorProto::TYPE_ENUM:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ return "sfixed32";
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ return "sfixed64";
+ case FieldDescriptorProto::TYPE_SINT32:
+ return "sint32";
+ case FieldDescriptorProto::TYPE_SINT64:
+ return "sint64";
+ default:
+ // won't happen
+ return "void";
+ }
+}
+
+static uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+ // Number
+ uint64_t result = (uint32_t)field.number();
+
+ // Type
+ 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:
+ ;
+ }
+
+ // Count
+ if (field.options().packed()) {
+ result |= FIELD_COUNT_PACKED;
+ } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+ result |= FIELD_COUNT_REPEATED;
+ } else {
+ result |= FIELD_COUNT_SINGLE;
+ }
+
+ return result;
+}
+
+/**
+ * Write a field.
+ */
+static void
+write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
+{
+ string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
+ ? "optional " : "";
+ string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
+ ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed()
+ ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
+{
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i=0; i<N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i=0; i<N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i=0; i<N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ */
+static void
+write_file(stringstream& text, const FileDescriptorProto& file_descriptor)
+{
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API, but they should all be available for testing.
+ text << "/** @hide */" << endl;
+ text << "@android.annotation.TestApi" << endl;
+
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+
+ int N;
+ const string indented = indent_more("");
+
+ N = file_descriptor.enum_type_size();
+ for (int i=0; i<N; i++) {
+ write_enum(text, file_descriptor.enum_type(i), indented);
+ }
+
+ N = file_descriptor.message_type_size();
+ for (int i=0; i<N; i++) {
+ write_message(text, file_descriptor.message_type(i), indented);
+ }
+
+ text << "}" << endl;
+}
+
+/**
+ * Main.
+ */
+int
+main(int argc, char const*const* argv)
+{
+ (void)argc;
+ (void)argv;
+
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+ CodeGeneratorRequest request;
+ CodeGeneratorResponse response;
+
+ // Read the request
+ request.ParseFromIstream(&cin);
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i=0; i<N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ // Generate the text
+ stringstream text;
+ write_file(text, file_descriptor);
+
+ // Put the text in the response
+ CodeGeneratorResponse::File* file_response = response.add_file();
+ file_response->set_name(make_file_name(file_descriptor));
+ file_response->set_content(text.str());
+
+ cerr << "writing file: " << file_response->name() << endl;
+ }
+ }
+
+ // If we had errors, don't write the response. Print the errors and exit.
+ if (ERRORS.HasErrors()) {
+ ERRORS.Print();
+ return 1;
+ }
+
+ // If we didn't have errors, write the response and exit happily.
+ response.SerializeToOstream(&cout);
+ return 0;
+}
diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp
new file mode 100644
index 0000000..cc738c4
--- /dev/null
+++ b/tools/streaming_proto/string_utils.cpp
@@ -0,0 +1,95 @@
+
+#include "string_utils.h"
+#include <iostream>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+string
+to_camel_case(const string& str)
+{
+ string result;
+ const int N = str.size();
+ result.reserve(N);
+ bool capitalize_next = true;
+ for (int i=0; i<N; i++) {
+ char c = str[i];
+ if (c == '_') {
+ capitalize_next = true;
+ } else {
+ if (capitalize_next && c >= 'a' && c <= 'z') {
+ c = 'A' + c - 'a';
+ capitalize_next = false;
+ } else if (c >= 'A' && c <= 'Z') {
+ capitalize_next = false;
+ } else if (c >= '0' && c <= '9') {
+ capitalize_next = true;
+ } else {
+ // All other characters (e.g. non-latin) count as capital.
+ capitalize_next = false;
+ }
+ result += c;
+ }
+ }
+ return result;
+}
+
+string
+make_constant_name(const string& str)
+{
+ string result;
+ const int N = str.size();
+ bool underscore_next = false;
+ for (int i=0; i<N; i++) {
+ char c = str[i];
+ if (c >= 'A' && c <= 'Z') {
+ if (underscore_next) {
+ result += '_';
+ underscore_next = false;
+ }
+ } else if (c >= 'a' && c <= 'z') {
+ c = 'A' + c - 'a';
+ underscore_next = true;
+ } else if (c == '_') {
+ underscore_next = false;
+ }
+ result += c;
+ }
+ return result;
+}
+
+string
+file_base_name(const string& str)
+{
+ size_t start = str.rfind('/');
+ if (start == string::npos) {
+ start = 0;
+ } else {
+ start++;
+ }
+ size_t end = str.find('.', start);
+ if (end == string::npos) {
+ end = str.size();
+ }
+ return str.substr(start, end-start);
+}
+
+string
+replace_string(const string& str, const char replace, const char with)
+{
+ string result(str);
+ const int N = result.size();
+ for (int i=0; i<N; i++) {
+ if (result[i] == replace) {
+ result[i] = with;
+ }
+ }
+ return result;
+}
+
+} // namespace javastream_proto
+} // namespace android
+
+
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
new file mode 100644
index 0000000..ffe83ca
--- /dev/null
+++ b/tools/streaming_proto/string_utils.h
@@ -0,0 +1,32 @@
+#include <string>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+/**
+ * Capitalizes the string, removes underscores and makes the next letter
+ * capitalized, and makes the letter following numbers capitalized.
+ */
+string to_camel_case(const string& str);
+
+/**
+ * Capitalize and insert underscores for CamelCase.
+ */
+string make_constant_name(const string& str);
+
+/**
+ * Returns the part of a file name that isn't a path and isn't a type suffix.
+ */
+string file_base_name(const string& str);
+
+/**
+ * Replace all occurances of 'replace' with 'with'.
+ */
+string replace_string(const string& str, const char replace, const char with);
+
+
+} // namespace javastream_proto
+} // namespace android
+
diff --git a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java b/tools/streaming_proto/test/imported.proto
similarity index 69%
copy from core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
copy to tools/streaming_proto/test/imported.proto
index fb76e67..05c8f0c 100644
--- a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
+++ b/tools/streaming_proto/test/imported.proto
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.print.mockservice;
+syntax = "proto2";
-import android.app.Activity;
-import android.os.Bundle;
+package com.android.streaming_proto_test;
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
+/**
+ * Message that is used from the other file.
+ */
+message ImportedMessage {
+ optional int32 data = 1;
+};
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 0000000..1246f53
--- /dev/null
+++ b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,7 @@
+package com.android.streaming_proto_test;
+
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/test.proto
new file mode 100644
index 0000000..de80ed6
--- /dev/null
+++ b/tools/streaming_proto/test/test.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/tools/streaming_proto/test/imported.proto";
+
+package com.android.streaming_proto_test;
+
+/**
+ * Enum that outside the scope of any classes.
+ */
+enum Outside {
+ OUTSIDE_0 = 0;
+ OUTSIDE_1 = 1;
+};
+
+message Sibling {
+ optional int32 int32_field = 1;
+}
+
+/**
+ * Message with all of the field types.
+ */
+message All {
+ /**
+ * Enum that is inside the scope of a class.
+ */
+ enum Inside {
+ option allow_alias = true;
+ INSIDE_0 = 0;
+ INSIDE_1 = 1;
+ INSIDE_1A = 1;
+ };
+
+ /**
+ * Message that is recursive.
+ */
+ message Nested {
+ optional int32 data = 10001;
+ optional Nested nested = 10002;
+ };
+
+ optional double double_field = 10;
+ repeated double double_field_repeated = 11;
+ repeated double double_field_packed = 12 [packed=true];
+
+ optional float float_field = 20;
+ repeated float float_field_repeated = 21;
+ repeated float float_field_packed = 22 [packed=true];
+
+ optional int32 int32_field = 30;
+ repeated int32 int32_field_repeated = 31;
+ repeated int32 int32_field_packed = 32 [packed=true];
+
+ optional int64 int64_field = 40;
+ repeated int64 int64_field_repeated = 41;
+ repeated int64 int64_field_packed = 42 [packed=true];
+
+ optional uint32 uint32_field = 50;
+ repeated uint32 uint32_field_repeated = 51;
+ repeated uint32 uint32_field_packed = 52 [packed=true];
+
+ optional uint64 uint64_field = 60;
+ repeated uint64 uint64_field_repeated = 61;
+ repeated uint64 uint64_field_packed = 62 [packed=true];
+
+ optional sint32 sint32_field = 70;
+ repeated sint32 sint32_field_repeated = 71;
+ repeated sint32 sint32_field_packed = 72 [packed=true];
+
+ optional sint64 sint64_field = 80;
+ repeated sint64 sint64_field_repeated = 81;
+ repeated sint64 sint64_field_packed = 82 [packed=true];
+
+ optional fixed32 fixed32_field = 90;
+ repeated fixed32 fixed32_field_repeated = 91;
+ repeated fixed32 fixed32_field_packed = 92 [packed=true];
+
+ optional fixed64 fixed64_field = 100;
+ repeated fixed64 fixed64_field_repeated = 101;
+ repeated fixed64 fixed64_field_packed = 102 [packed=true];
+
+ optional sfixed32 sfixed32_field = 110;
+ repeated sfixed32 sfixed32_field_repeated = 111;
+ repeated sfixed32 sfixed32_field_packed = 112 [packed=true];
+
+ optional sfixed64 sfixed64_field = 120;
+ repeated sfixed64 sfixed64_field_repeated = 121;
+ repeated sfixed64 sfixed64_field_packed = 122 [packed=true];
+
+ optional bool bool_field = 130;
+ repeated bool bool_field_repeated = 131;
+ repeated bool bool_field_packed = 132 [packed=true];
+
+ optional string string_field = 140;
+ repeated string string_field_repeated = 141;
+
+ optional bytes bytes_field = 150;
+ repeated bytes bytes_field_repeated = 151;
+
+ optional Outside outside_field = 160;
+ repeated Outside outside_field_repeated = 161;
+ repeated Outside outside_field_packed = 162 [packed=true];
+
+ optional Nested nested_field = 170;
+ repeated Nested nested_field_repeated = 171;
+
+ optional ImportedMessage imported_field = 180;
+ repeated ImportedMessage imported_field_repeated = 181;
+};
diff --git a/wifi/java/android/net/wifi/ParcelUtil.java b/wifi/java/android/net/wifi/ParcelUtil.java
new file mode 100644
index 0000000..a26877d
--- /dev/null
+++ b/wifi/java/android/net/wifi/ParcelUtil.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 android.net.wifi;
+
+import android.os.Parcel;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * Provides utilities for writing/reading a non-Parcelable objects to/from
+ * a Parcel object.
+ *
+ * @hide
+ */
+public class ParcelUtil {
+ /**
+ * Write a PrivateKey object |key| to the specified Parcel |dest|.
+ *
+ * Below is the data format:
+ * |algorithm| -> String of algorithm name
+ * |endcodedKey| -> byte[] of key data
+ *
+ * For a null PrivateKey object, a null string will be written to |algorithm| and
+ * |encodedKey| will be skipped. Since a PrivateKey can only be constructed with
+ * a valid algorithm String.
+ *
+ * @param dest Parcel object to write to
+ * @param key PrivateKey object to read from.
+ */
+ public static void writePrivateKey(Parcel dest, PrivateKey key) {
+ if (key == null) {
+ dest.writeString(null);
+ return;
+ }
+
+ dest.writeString(key.getAlgorithm());
+ dest.writeByteArray(key.getEncoded());
+ }
+
+ /**
+ * Read/create a PrivateKey object from a specified Parcel object |in|.
+ *
+ * Refer to the function above for the expected data format.
+ *
+ * @param in Parcel object to read from
+ * @return a PrivateKey object or null
+ */
+ public static PrivateKey readPrivateKey(Parcel in) {
+ String algorithm = in.readString();
+ if (algorithm == null) {
+ return null;
+ }
+
+ byte[] userKeyBytes = in.createByteArray();
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(userKeyBytes));
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Write a X509Certificate object |cert| to a Parcel object |dest|.
+ * The data being written to the Parcel is just a byte[] of the encoded certificate data.
+ *
+ * @param dest Parcel object to write to
+ * @param cert X509Certificate object to read from
+ */
+ public static void writeCertificate(Parcel dest, X509Certificate cert) {
+ byte[] certBytes = null;
+ if (cert != null) {
+ try {
+ certBytes = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ /* empty, write null. */
+ }
+ }
+ dest.writeByteArray(certBytes);
+ }
+
+ /**
+ * Read/create a X509Certificate object from a specified Parcel object |in|.
+ *
+ * @param in Parcel object to read from
+ * @return a X509Certficate object or null
+ */
+ public static X509Certificate readCertificate(Parcel in) {
+ byte[] certBytes = in.createByteArray();
+ if (certBytes == null) {
+ return null;
+ }
+
+ try {
+ CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cFactory
+ .generateCertificate(new ByteArrayInputStream(certBytes));
+ } catch (CertificateException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Write an array of X509Certificate objects |certs| to a Parcel object |dest|.
+ * The data being written to the Parcel are consist of an integer indicating
+ * the size of the array and the certificates data. Certificates data will be
+ * skipped for a null array or size of 0 array.
+ *
+ * @param dest Parcel object to write to
+ * @param certs array of X509Certificate objects to read from
+ */
+ public static void writeCertificates(Parcel dest, X509Certificate[] certs) {
+ if (certs == null || certs.length == 0) {
+ dest.writeInt(0);
+ return;
+ }
+
+ dest.writeInt(certs.length);
+ for (int i = 0; i < certs.length; i++) {
+ writeCertificate(dest, certs[i]);
+ }
+ }
+
+ /**
+ * Read/create an array of X509Certificate objects from a specified Parcel object |in|.
+ *
+ * @param in Parcel object to read from
+ * @return X509Certficate[] or null
+ */
+ public static X509Certificate[] readCertificates(Parcel in) {
+ int length = in.readInt();
+ if (length == 0) {
+ return null;
+ }
+
+ X509Certificate[] certs = new X509Certificate[length];
+ for (int i = 0; i < length; i++) {
+ certs[i] = readCertificate(in);
+ }
+ return certs;
+ }
+}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 67cf107..465addf 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -139,12 +139,6 @@
public long seen;
/**
- * If the scan result is a valid autojoin candidate
- * {@hide}
- */
- public int isAutoJoinCandidate;
-
- /**
* @hide
* Update RSSI of the scan result
* @param previousRssi
@@ -452,7 +446,6 @@
numConnection = source.numConnection;
numUsage = source.numUsage;
numIpConfigFailures = source.numIpConfigFailures;
- isAutoJoinCandidate = source.isAutoJoinCandidate;
venueName = source.venueName;
operatorFriendlyName = source.operatorFriendlyName;
flags = source.flags;
@@ -530,7 +523,6 @@
dest.writeInt(numConnection);
dest.writeInt(numUsage);
dest.writeInt(numIpConfigFailures);
- dest.writeInt(isAutoJoinCandidate);
dest.writeString((venueName != null) ? venueName.toString() : "");
dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
dest.writeLong(this.flags);
@@ -600,7 +592,6 @@
sr.numConnection = in.readInt();
sr.numUsage = in.readInt();
sr.numIpConfigFailures = in.readInt();
- sr.isAutoJoinCandidate = in.readInt();
sr.venueName = in.readString();
sr.operatorFriendlyName = in.readString();
sr.flags = in.readLong();
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index c0e8bc2..e410a9c 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -183,48 +183,14 @@
dest.writeInt(mEapMethod);
dest.writeInt(mPhase2Method);
- writeCertificates(dest, mCaCerts);
-
- if (mClientPrivateKey != null) {
- String algorithm = mClientPrivateKey.getAlgorithm();
- byte[] userKeyBytes = mClientPrivateKey.getEncoded();
- dest.writeInt(userKeyBytes.length);
- dest.writeByteArray(userKeyBytes);
- dest.writeString(algorithm);
- } else {
- dest.writeInt(0);
- }
-
- writeCertificate(dest, mClientCertificate);
- }
-
- private void writeCertificates(Parcel dest, X509Certificate[] cert) {
- if (cert != null && cert.length != 0) {
- dest.writeInt(cert.length);
- for (int i = 0; i < cert.length; i++) {
- writeCertificate(dest, cert[i]);
- }
- } else {
- dest.writeInt(0);
- }
- }
-
- private void writeCertificate(Parcel dest, X509Certificate cert) {
- if (cert != null) {
- try {
- byte[] certBytes = cert.getEncoded();
- dest.writeInt(certBytes.length);
- dest.writeByteArray(certBytes);
- } catch (CertificateEncodingException e) {
- dest.writeInt(0);
- }
- } else {
- dest.writeInt(0);
- }
+ ParcelUtil.writeCertificates(dest, mCaCerts);
+ ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
+ ParcelUtil.writeCertificate(dest, mClientCertificate);
}
public static final Creator<WifiEnterpriseConfig> CREATOR =
new Creator<WifiEnterpriseConfig>() {
+ @Override
public WifiEnterpriseConfig createFromParcel(Parcel in) {
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
int count = in.readInt();
@@ -236,58 +202,13 @@
enterpriseConfig.mEapMethod = in.readInt();
enterpriseConfig.mPhase2Method = in.readInt();
- enterpriseConfig.mCaCerts = readCertificates(in);
-
- PrivateKey userKey = null;
- int len = in.readInt();
- if (len > 0) {
- try {
- byte[] bytes = new byte[len];
- in.readByteArray(bytes);
- String algorithm = in.readString();
- KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
- userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
- } catch (NoSuchAlgorithmException e) {
- userKey = null;
- } catch (InvalidKeySpecException e) {
- userKey = null;
- }
- }
-
- enterpriseConfig.mClientPrivateKey = userKey;
- enterpriseConfig.mClientCertificate = readCertificate(in);
+ enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
+ enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
+ enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
return enterpriseConfig;
}
- private X509Certificate[] readCertificates(Parcel in) {
- X509Certificate[] certs = null;
- int len = in.readInt();
- if (len > 0) {
- certs = new X509Certificate[len];
- for (int i = 0; i < len; i++) {
- certs[i] = readCertificate(in);
- }
- }
- return certs;
- }
-
- private X509Certificate readCertificate(Parcel in) {
- X509Certificate cert = null;
- int len = in.readInt();
- if (len > 0) {
- try {
- byte[] bytes = new byte[len];
- in.readByteArray(bytes);
- CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
- cert = (X509Certificate) cFactory
- .generateCertificate(new ByteArrayInputStream(bytes));
- } catch (CertificateException e) {
- cert = null;
- }
- }
- return cert;
- }
-
+ @Override
public WifiEnterpriseConfig[] newArray(int size) {
return new WifiEnterpriseConfig[size];
}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.aidl b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.aidl
new file mode 100644
index 0000000..6b1cea8
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.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.net.wifi.hotspot2;
+
+parcelable PasspointConfiguration;
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
new file mode 100644
index 0000000..18aae53
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -0,0 +1,78 @@
+/**
+ * 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.net.wifi.hotspot2;
+
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSP;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * Class representing Passpoint configuration. This contains configurations specified in
+ * PerProviderSubscription (PPS) Management Object (MO) tree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * Currently, only HomeSP and Credential subtrees are supported.
+ *
+ * @hide
+ */
+public final class PasspointConfiguration implements Parcelable {
+ public HomeSP homeSp = null;
+ public Credential credential = null;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(homeSp, flags);
+ dest.writeParcelable(credential, flags);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof PasspointConfiguration)) {
+ return false;
+ }
+ PasspointConfiguration that = (PasspointConfiguration) thatObject;
+ return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
+ (credential == null ? that.credential == null :
+ credential.equals(that.credential));
+ }
+
+ public static final Creator<PasspointConfiguration> CREATOR =
+ new Creator<PasspointConfiguration>() {
+ @Override
+ public PasspointConfiguration createFromParcel(Parcel in) {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = in.readParcelable(null);
+ config.credential = in.readParcelable(null);
+ return config;
+ }
+ @Override
+ public PasspointConfiguration[] newArray(int size) {
+ return new PasspointConfiguration[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
new file mode 100644
index 0000000..65a49ea
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -0,0 +1,786 @@
+/**
+ * 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.net.wifi.hotspot2.omadm;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSP;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Utility class for converting OMA-DM (Open Mobile Alliance's Device Management)
+ * PPS-MO (PerProviderSubscription Management Object) XML tree to a
+ * {@link PasspointConfiguration} object.
+ *
+ * Currently this only supports PerProviderSubscription/HomeSP and
+ * PerProviderSubscription/Credential subtree for Hotspot 2.0 Release 1 support.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * Below is a sample XML string for a Release 1 PPS MO tree:
+ *
+ * <MgmtTree xmlns="syncml:dmddf1.2">
+ * <VerDTD>1.2</VerDTD>
+ * <Node>
+ * <NodeName>PerProviderSubscription</NodeName>
+ * <RTProperties>
+ * <Type>
+ * <DDFName>urn:wfa:mo:hotspot2dot0perprovidersubscription:1.0</DDFName>
+ * </Type>
+ * </RTProperties>
+ * <Node>
+ * <NodeName>i001</NodeName>
+ * <Node>
+ * <NodeName>HomeSP</NodeName>
+ * <Node>
+ * <NodeName>FriendlyName</NodeName>
+ * <Value>Century House</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>FQDN</NodeName>
+ * <Value>mi6.co.uk</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>RoamingConsortiumOI</NodeName>
+ * <Value>112233,445566</Value>
+ * </Node>
+ * </Node>
+ * <Node>
+ * <NodeName>Credential</NodeName>
+ * <Node>
+ * <NodeName>Realm</NodeName>
+ * <Value>shaken.stirred.com</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>UsernamePassword</NodeName>
+ * <Node>
+ * <NodeName>Username</NodeName>
+ * <Value>james</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>Password</NodeName>
+ * <Value>Ym9uZDAwNw==</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>EAPMethod</NodeName>
+ * <Node>
+ * <NodeName>EAPType</NodeName>
+ * <Value>21</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>InnerMethod</NodeName>
+ * <Value>MS-CHAP-V2</Value>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </MgmtTree>
+ *
+ * @hide
+ */
+public final class PPSMOParser {
+ private static final String TAG = "PPSMOParser";
+
+ /**
+ * XML tags expected in the PPS MO (PerProviderSubscription Management Object) XML tree.
+ */
+ private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
+ private static final String TAG_VER_DTD = "VerDTD";
+ private static final String TAG_NODE = "Node";
+ private static final String TAG_NODE_NAME = "NodeName";
+ private static final String TAG_RT_PROPERTIES = "RTProperties";
+ private static final String TAG_TYPE = "Type";
+ private static final String TAG_DDF_NAME = "DDFName";
+ private static final String TAG_VALUE = "Value";
+
+ /**
+ * Name for PerProviderSubscription node.
+ */
+ private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
+
+ /**
+ * Fields under HomeSP subtree.
+ */
+ private static final String NODE_HOMESP = "HomeSP";
+ private static final String NODE_FQDN = "FQDN";
+ private static final String NODE_FRIENDLY_NAME = "FriendlyName";
+ private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+
+ /**
+ * Fields under Credential subtree.
+ */
+ private static final String NODE_CREDENTIAL = "Credential";
+ private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
+ private static final String NODE_USERNAME = "Username";
+ private static final String NODE_PASSWORD = "Password";
+ private static final String NODE_EAP_METHOD = "EAPMethod";
+ private static final String NODE_EAP_TYPE = "EAPType";
+ private static final String NODE_INNER_METHOD = "InnerMethod";
+ private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
+ private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
+ private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
+ private static final String NODE_REALM = "Realm";
+ private static final String NODE_SIM = "SIM";
+ private static final String NODE_SIM_IMSI = "IMSI";
+
+ /**
+ * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
+ */
+ private static final String PPS_MO_URN =
+ "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
+
+ /**
+ * Exception for generic parsing errors.
+ */
+ private static class ParsingException extends Exception {
+ public ParsingException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Class representing a node within the PerProviderSubscription tree.
+ * This is used to flatten out and eliminate the extra layering in the XMLNode tree,
+ * to make the data parsing easier and cleaner.
+ *
+ * A PPSNode can be an internal or a leaf node, but not both.
+ *
+ */
+ private static abstract class PPSNode {
+ private final String mName;
+ public PPSNode(String name) {
+ mName = name;
+ }
+
+ /**
+ * @return the name of the node
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Applies for internal node only.
+ *
+ * @return the list of children nodes.
+ */
+ public abstract List<PPSNode> getChildren();
+
+ /**
+ * Applies for leaf node only.
+ *
+ * @return the string value of the node
+ */
+ public abstract String getValue();
+
+ /**
+ * @return a flag indicating if this is a leaf or an internal node
+ */
+ public abstract boolean isLeaf();
+ }
+
+ /**
+ * Class representing a leaf node in a PPS (PerProviderSubscription) tree.
+ */
+ private static class LeafNode extends PPSNode {
+ private final String mValue;
+ public LeafNode(String nodeName, String value) {
+ super(nodeName);
+ mValue = value;
+ }
+
+ @Override
+ public String getValue() {
+ return mValue;
+ }
+
+ @Override
+ public List<PPSNode> getChildren() {
+ return null;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return true;
+ }
+ }
+
+ /**
+ * Class representing an internal node in a PPS (PerProviderSubscription) tree.
+ */
+ private static class InternalNode extends PPSNode {
+ private final List<PPSNode> mChildren;
+ public InternalNode(String nodeName, List<PPSNode> children) {
+ super(nodeName);
+ mChildren = children;
+ }
+
+ @Override
+ public String getValue() {
+ return null;
+ }
+
+ @Override
+ public List<PPSNode> getChildren() {
+ return mChildren;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return false;
+ }
+ }
+
+ /**
+ * Convert a XML string representation of a PPS MO (PerProviderSubscription
+ * Management Object) tree to a {@link PasspointConfiguration} object.
+ *
+ * @param xmlString XML string representation of a PPS MO tree
+ * @return {@link PasspointConfiguration} or null
+ */
+ public static PasspointConfiguration parseMOText(String xmlString) {
+ // Convert the XML string to a XML tree.
+ XMLParser xmlParser = new XMLParser();
+ XMLNode root = null;
+ try {
+ root = xmlParser.parse(xmlString);
+ } catch(IOException | SAXException e) {
+ return null;
+ }
+ if (root == null) {
+ return null;
+ }
+
+ // Verify root node is a "MgmtTree" node.
+ if (root.getTag() != TAG_MANAGEMENT_TREE) {
+ Log.e(TAG, "Root is not a MgmtTree");
+ return null;
+ }
+
+ String verDtd = null; // Used for detecting duplicate VerDTD element.
+ PasspointConfiguration config = null;
+ for (XMLNode child : root.getChildren()) {
+ switch(child.getTag()) {
+ case TAG_VER_DTD:
+ if (verDtd != null) {
+ Log.e(TAG, "Duplicate VerDTD element");
+ return null;
+ }
+ verDtd = child.getText();
+ break;
+ case TAG_NODE:
+ if (config != null) {
+ Log.e(TAG, "Unexpected multiple Node element under MgmtTree");
+ return null;
+ }
+ try {
+ config = parsePpsNode(child);
+ } catch (ParsingException e) {
+ Log.e(TAG, e.getMessage());
+ return null;
+ }
+ break;
+ default:
+ Log.e(TAG, "Unknown node: " + child.getTag());
+ return null;
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Parse a PerProviderSubscription node. Below is the format of the XML tree (with
+ * each XML element represent a node in the tree):
+ *
+ * <Node>
+ * <NodeName>PerProviderSubscription</NodeName>
+ * <RTProperties>
+ * ...
+ * </RTPProperties>
+ * <Node>
+ * ...
+ * </Node>
+ * </Node>
+ *
+ * @param node XMLNode that contains PerProviderSubscription node.
+ * @return PasspointConfiguration or null
+ * @throws ParsingException
+ */
+ private static PasspointConfiguration parsePpsNode(XMLNode node)
+ throws ParsingException {
+ PasspointConfiguration config = null;
+ String nodeName = null;
+ for (XMLNode child : node.getChildren()) {
+ switch (child.getTag()) {
+ case TAG_NODE_NAME:
+ if (nodeName != null) {
+ throw new ParsingException("Duplicant NodeName: " + child.getText());
+ }
+ nodeName = child.getText();
+ if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
+ throw new ParsingException("Unexpected NodeName: " + nodeName);
+ }
+ break;
+ case TAG_NODE:
+ // Only one PerProviderSubscription instance is expected and allowed.
+ if (config != null) {
+ throw new ParsingException("Multiple PPS instance");
+ }
+ // Convert the XML tree to a PPS tree.
+ PPSNode ppsInstanceRoot = buildPpsNode(child);
+ config = parsePpsInstance(ppsInstanceRoot);
+ break;
+ case TAG_RT_PROPERTIES:
+ // Parse and verify URN stored in the RT (Run Time) Properties.
+ String urn = parseUrn(child);
+ if (!TextUtils.equals(urn, PPS_MO_URN)) {
+ throw new ParsingException("Unknown URN: " + urn);
+ }
+ break;
+ default:
+ throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Parse the URN stored in the RTProperties. Below is the format of the RTPProperties node:
+ *
+ * <RTProperties>
+ * <Type>
+ * <DDFName>urn:...</DDFName>
+ * </Type>
+ * </RTProperties>
+ *
+ * @param node XMLNode that contains RTProperties node.
+ * @return URN String of URN.
+ * @throws ParsingException
+ */
+ private static String parseUrn(XMLNode node) throws ParsingException {
+ if (node.getChildren().size() != 1)
+ throw new ParsingException("Expect RTPProperties node to only have one child");
+
+ XMLNode typeNode = node.getChildren().get(0);
+ if (typeNode.getChildren().size() != 1) {
+ throw new ParsingException("Expect Type node to only have one child");
+ }
+ if (!TextUtils.equals(typeNode.getTag(), TAG_TYPE)) {
+ throw new ParsingException("Unexpected tag for Type: " + typeNode.getTag());
+ }
+
+ XMLNode ddfNameNode = typeNode.getChildren().get(0);
+ if (!ddfNameNode.getChildren().isEmpty()) {
+ throw new ParsingException("Expect DDFName node to have no child");
+ }
+ if (!TextUtils.equals(ddfNameNode.getTag(), TAG_DDF_NAME)) {
+ throw new ParsingException("Unexpected tag for DDFName: " + ddfNameNode.getTag());
+ }
+
+ return ddfNameNode.getText();
+ }
+
+ /**
+ * Convert a XML tree represented by XMLNode to a PPS (PerProviderSubscription) instance tree
+ * represented by PPSNode. This flattens out the XML tree to allow easier and cleaner parsing
+ * of the PPS configuration data. Only three types of XML tag are expected: "NodeName",
+ * "Node", and "Value".
+ *
+ * The original XML tree (each XML element represent a node):
+ *
+ * <Node>
+ * <NodeName>root</NodeName>
+ * <Node>
+ * <NodeName>child1</NodeName>
+ * <Value>value1</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>child2</NodeName>
+ * <Node>
+ * <NodeName>grandchild1</NodeName>
+ * ...
+ * </Node>
+ * </Node>
+ * ...
+ * </Node>
+ *
+ * The converted PPS tree:
+ *
+ * [root] --- [child1, value1]
+ * |
+ * ---------[child2] --------[grandchild1] --- ...
+ *
+ * @param node XMLNode pointed to the root of a XML tree
+ * @return PPSNode pointing to the root of a PPS tree
+ * @throws ParsingException
+ */
+ private static PPSNode buildPpsNode(XMLNode node) throws ParsingException {
+ String nodeName = null;
+ String nodeValue = null;
+ List<PPSNode> childNodes = new ArrayList<PPSNode>();
+ // Names of parsed child nodes, use for detecting multiple child nodes with the same name.
+ Set<String> parsedNodes = new HashSet<String>();
+
+ for (XMLNode child : node.getChildren()) {
+ String tag = child.getTag();
+ if (TextUtils.equals(tag, TAG_NODE_NAME)) {
+ if (nodeName != null) {
+ throw new ParsingException("Duplicate NodeName node");
+ }
+ nodeName = child.getText();
+ } else if (TextUtils.equals(tag, TAG_NODE)) {
+ PPSNode ppsNode = buildPpsNode(child);
+ if (parsedNodes.contains(ppsNode.getName())) {
+ throw new ParsingException("Duplicate node: " + ppsNode.getName());
+ }
+ parsedNodes.add(ppsNode.getName());
+ childNodes.add(ppsNode);
+ } else if (TextUtils.equals(tag, TAG_VALUE)) {
+ if (nodeValue != null) {
+ throw new ParsingException("Duplicate Value node");
+ }
+ nodeValue = child.getText();
+ } else {
+ throw new ParsingException("Unknown tag: " + tag);
+ }
+ }
+
+ if (nodeName == null) {
+ throw new ParsingException("Invalid node: missing NodeName");
+ }
+ if (nodeValue == null && childNodes.size() == 0) {
+ throw new ParsingException("Invalid node: " + nodeName +
+ " missing both value and children");
+ }
+ if (nodeValue != null && childNodes.size() > 0) {
+ throw new ParsingException("Invalid node: " + nodeName +
+ " contained both value and children");
+ }
+
+ if (nodeValue != null) {
+ return new LeafNode(nodeName, nodeValue);
+ }
+ return new InternalNode(nodeName, childNodes);
+ }
+
+ /**
+ * Return the value of a PPSNode. An exception will be thrown if the given node
+ * is not a leaf node.
+ *
+ * @param node PPSNode to retrieve the value from
+ * @return String representing the value of the node
+ * @throws ParsingException
+ */
+ private static String getPpsNodeValue(PPSNode node) throws ParsingException {
+ if (!node.isLeaf()) {
+ throw new ParsingException("Cannot get value from a non-leaf node: " + node.getName());
+ }
+ return node.getValue();
+ }
+
+ /**
+ * Parse a PPS (PerProviderSubscription) configurations from a PPS tree.
+ *
+ * @param root PPSNode representing the root of the PPS tree
+ * @return PasspointConfiguration
+ * @throws ParsingException
+ */
+ private static PasspointConfiguration parsePpsInstance(PPSNode root)
+ throws ParsingException {
+ if (root.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PPS instance");
+ }
+
+ PasspointConfiguration config = new PasspointConfiguration();
+ for (PPSNode child : root.getChildren()) {
+ switch(child.getName()) {
+ case NODE_HOMESP:
+ config.homeSp = parseHomeSP(child);
+ break;
+ case NODE_CREDENTIAL:
+ config.credential = parseCredential(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node: " + child.getName());
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP subtree
+ * @return HomeSP
+ * @throws ParsingException
+ */
+ private static HomeSP parseHomeSP(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeSP");
+ }
+
+ HomeSP homeSp = new HomeSP();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN:
+ homeSp.fqdn = getPpsNodeValue(child);
+ break;
+ case NODE_FRIENDLY_NAME:
+ homeSp.friendlyName = getPpsNodeValue(child);
+ break;
+ case NODE_ROAMING_CONSORTIUM_OI:
+ homeSp.roamingConsortiumOIs =
+ parseRoamingConsortiumOI(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under HomeSP: " + child.getName());
+ }
+ }
+ return homeSp;
+ }
+
+ /**
+ * Parse the roaming consortium OI string, which contains a list of OIs separated by ",".
+ *
+ * @param oiStr string containing list of OIs (Organization Identifiers) separated by ","
+ * @return long[]
+ * @throws ParsingException
+ */
+ private static long[] parseRoamingConsortiumOI(String oiStr)
+ throws ParsingException {
+ String[] oiStrArray = oiStr.split(",");
+ long[] oiArray = new long[oiStrArray.length];
+ for (int i = 0; i < oiStrArray.length; i++) {
+ try {
+ oiArray[i] = Long.parseLong(oiStrArray[i], 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid OI: " + oiStrArray[i]);
+ }
+ }
+ return oiArray;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
+ * @return Credential
+ * @throws ParsingException
+ */
+ private static Credential parseCredential(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeSP");
+ }
+
+ Credential credential = new Credential();
+ for (PPSNode child: node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_USERNAME_PASSWORD:
+ credential.userCredential = parseUserCredential(child);
+ break;
+ case NODE_DIGITAL_CERTIFICATE:
+ credential.certCredential = parseCertificateCredential(child);
+ break;
+ case NODE_REALM:
+ credential.realm = getPpsNodeValue(child);
+ break;
+ case NODE_SIM:
+ credential.simCredential = parseSimCredential(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under Credential: " +
+ child.getName());
+ }
+ }
+ return credential;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/UsernamePassword subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Credential/UsernamePassword subtree
+ * @return Credential.UserCredential
+ * @throws ParsingException
+ */
+ private static Credential.UserCredential parseUserCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsernamePassword");
+ }
+
+ Credential.UserCredential userCred = new Credential.UserCredential();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_USERNAME:
+ userCred.username = getPpsNodeValue(child);
+ break;
+ case NODE_PASSWORD:
+ userCred.password = getPpsNodeValue(child);
+ break;
+ case NODE_EAP_METHOD:
+ parseEAPMethod(child, userCred);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsernamPassword: " +
+ child.getName());
+ }
+ }
+ return userCred;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/UsernamePassword/EAPMethod
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Credential/UsernamePassword/EAPMethod subtree
+ * @param userCred UserCredential to be updated with EAP method values.
+ * @throws ParsingException
+ */
+ private static void parseEAPMethod(PPSNode node, Credential.UserCredential userCred)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for EAPMethod");
+ }
+
+ for (PPSNode child : node.getChildren()) {
+ switch(child.getName()) {
+ case NODE_EAP_TYPE:
+ userCred.eapType = parseInteger(getPpsNodeValue(child));
+ break;
+ case NODE_INNER_METHOD:
+ userCred.nonEapInnerMethod = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/DigitalCertificate subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Credential/DigitalCertificate subtree
+ * @return Credential.CertificateCredential
+ * @throws ParsingException
+ */
+ private static Credential.CertificateCredential parseCertificateCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for DigitalCertificate");
+ }
+
+ Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CERTIFICATE_TYPE:
+ certCred.certType = getPpsNodeValue(child);
+ break;
+ case NODE_CERT_SHA256_FINGERPRINT:
+ certCred.certSha256FingerPrint = parseHexString(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under DigitalCertificate: " +
+ child.getName());
+ }
+ }
+ return certCred;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/SIM subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Credential/SIM
+ * subtree
+ * @return Credential.SimCredential
+ * @throws ParsingException
+ */
+ private static Credential.SimCredential parseSimCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SIM");
+ }
+
+ Credential.SimCredential simCred = new Credential.SimCredential();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SIM_IMSI:
+ simCred.imsi = getPpsNodeValue(child);
+ break;
+ case NODE_EAP_TYPE:
+ simCred.eapType = parseInteger(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under SIM: " + child.getName());
+ }
+ }
+ return simCred;
+ }
+
+ /**
+ * Convert a hex string to a byte array.
+ *
+ * @param str String containing hex values
+ * @return byte[]
+ * @throws ParsingException
+ */
+ private static byte[] parseHexString(String str) throws ParsingException {
+ if ((str.length() & 1) == 1) {
+ throw new ParsingException("Odd length hex string: " + str.length());
+ }
+
+ byte[] result = new byte[str.length() / 2];
+ for (int i = 0; i < result.length; i++) {
+ int index = i * 2;
+ try {
+ result[i] = (byte) Integer.parseInt(str.substring(index, index + 2), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid hex string: " + str);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Parse an integer string.
+ *
+ * @param value String of integer value
+ * @return int
+ * @throws ParsingException
+ */
+ private static int parseInteger(String value) throws ParsingException {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid integer value: " + value);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java b/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java
new file mode 100644
index 0000000..e87698c
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java
@@ -0,0 +1,103 @@
+/**
+ * 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.net.wifi.hotspot2.omadm;
+
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class represent a node in an XML tree. Each node is an XML element.
+ * Used by {@link XMLParser} for parsing/converting each XML element to XMLNode.
+ *
+ * @hide
+ */
+public class XMLNode {
+ private final String mTag;
+ private final List<XMLNode> mChildren;
+ private final XMLNode mParent;
+ private StringBuilder mTextBuilder;
+ private String mText;
+
+ public XMLNode(XMLNode parent, String tag) {
+ mTag = tag;
+ mParent = parent;
+ mChildren = new ArrayList<>();
+ mTextBuilder = new StringBuilder();
+ mText = null;
+ }
+
+ /**
+ * Adding a text to this node. Invoked by {@link XMLParser#characters}.
+ *
+ * @param text String to be added
+ */
+ public void addText(String text) {
+ mTextBuilder.append(text);
+ }
+
+ /**
+ * Adding a child node to this node. Invoked by {@link XMLParser#startElement}.
+ *
+ * @param child XMLNode to be added
+ */
+ public void addChild(XMLNode child) {
+ mChildren.add(child);
+ }
+
+ /**
+ * Invoked when the end of the XML element is detected. Used for further processing
+ * of the text enclosed within this XML element. Invoked by {@link XMLParser#endElement}.
+ */
+ public void close() {
+ // Remove the leading and the trailing whitespaces.
+ mText = mTextBuilder.toString().trim();
+ mTextBuilder = null;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public XMLNode getParent() {
+ return mParent;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public List<XMLNode> getChildren() {
+ return mChildren;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof XMLNode)) {
+ return false;
+ }
+ XMLNode that = (XMLNode) thatObject;
+
+ return TextUtils.equals(mTag, that.mTag) &&
+ TextUtils.equals(mText, that.mText) &&
+ mChildren.equals(that.mChildren);
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/XMLParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/XMLParser.java
new file mode 100644
index 0000000..948052c
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/XMLParser.java
@@ -0,0 +1,108 @@
+/*
+ * 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.net.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.text.TextUtils;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Class for parsing an XML string to an XML tree represented by {@link XMLNode}.
+ *
+ * The original XML string:
+ * <root>
+ * <tag1>text1</tag1>
+ * <tag2>
+ * <tag3>text3</tag3>
+ * </tag2>
+ * </root>
+ *
+ * The XML tree representation:
+ * [root]
+ * |
+ * |
+ * [tag1, text1]-----|-----[tag2]
+ * |
+ * |
+ * [tag3, text3]
+ *
+ * @hide
+ */
+public class XMLParser extends DefaultHandler {
+ private XMLNode mRoot = null;
+ private XMLNode mCurrent = null;
+
+ public XMLNode parse(String text) throws IOException, SAXException {
+ if (TextUtils.isEmpty(text)) {
+ throw new IOException("XML string not provided");
+ }
+
+ // Reset pointers.
+ mRoot = null;
+ mCurrent = null;
+
+ try {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ parser.parse(new InputSource(new StringReader(text)), this);
+ return mRoot;
+ } catch (ParserConfigurationException pce) {
+ throw new SAXException(pce);
+ }
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ XMLNode parent = mCurrent;
+
+ mCurrent = new XMLNode(parent, qName);
+
+ if (mRoot == null) {
+ mRoot = mCurrent;
+ } else if (parent == null) {
+ throw new SAXException("More than one root nodes");
+ } else {
+ parent.addChild(mCurrent);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (!qName.equals(mCurrent.getTag())) {
+ throw new SAXException("End tag '" + qName + "' doesn't match current node: " +
+ mCurrent);
+ }
+
+ mCurrent.close();
+ mCurrent = mCurrent.getParent();
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ mCurrent.addText(new String(ch, start, length));
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Credential.aidl
new file mode 100644
index 0000000..3d8e833
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.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.net.wifi.hotspot2.pps;
+
+parcelable Credential;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
new file mode 100644
index 0000000..92dbd8a
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -0,0 +1,376 @@
+/**
+ * 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.net.wifi.hotspot2.pps;
+
+import android.net.wifi.ParcelUtil;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+/**
+ * Class representing Credential subtree in the PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * In addition to the fields in the Credential subtree, this will also maintain necessary
+ * information for the private key and certificates associated with this credential.
+ *
+ * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
+ *
+ * @hide
+ */
+public final class Credential implements Parcelable {
+ /**
+ * The realm associated with this credential. It will be used to determine
+ * if this credential can be used to authenticate with a given hotspot by
+ * comparing the realm specified in that hotspot's ANQP element.
+ */
+ public String realm = null;
+
+ /**
+ * Username-password based credential.
+ * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
+ */
+ public static final class UserCredential implements Parcelable {
+ /**
+ * Username of the credential.
+ */
+ public String username = null;
+
+ /**
+ * Base64-encoded password.
+ */
+ public String password = null;
+
+ /**
+ * EAP (Extensible Authentication Protocol) method type.
+ * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
+ * for valid values.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ public int eapType = Integer.MIN_VALUE;
+
+ /**
+ * Non-EAP inner authentication method.
+ */
+ public String nonEapInnerMethod = null;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(username);
+ dest.writeString(password);
+ dest.writeInt(eapType);
+ dest.writeString(nonEapInnerMethod);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof UserCredential)) {
+ return false;
+ }
+
+ UserCredential that = (UserCredential) thatObject;
+ return TextUtils.equals(username, that.username) &&
+ TextUtils.equals(password, that.password) &&
+ eapType == that.eapType &&
+ TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
+ }
+
+ public static final Creator<UserCredential> CREATOR =
+ new Creator<UserCredential>() {
+ @Override
+ public UserCredential createFromParcel(Parcel in) {
+ UserCredential userCredential = new UserCredential();
+ userCredential.username = in.readString();
+ userCredential.password = in.readString();
+ userCredential.eapType = in.readInt();
+ userCredential.nonEapInnerMethod = in.readString();
+ return userCredential;
+ }
+
+ @Override
+ public UserCredential[] newArray(int size) {
+ return new UserCredential[size];
+ }
+ };
+ }
+ public UserCredential userCredential = null;
+
+ /**
+ * Certificate based credential.
+ * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree.
+ */
+ public static final class CertificateCredential implements Parcelable {
+ /**
+ * Certificate type. Valid values are "802.1ar" and "x509v3".
+ */
+ public String certType = null;
+
+ /**
+ * The SHA-256 fingerprint of the certificate.
+ */
+ public byte[] certSha256FingerPrint = null;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(certType);
+ dest.writeByteArray(certSha256FingerPrint);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof CertificateCredential)) {
+ return false;
+ }
+
+ CertificateCredential that = (CertificateCredential) thatObject;
+ return TextUtils.equals(certType, that.certType) &&
+ Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+ }
+
+ public static final Creator<CertificateCredential> CREATOR =
+ new Creator<CertificateCredential>() {
+ @Override
+ public CertificateCredential createFromParcel(Parcel in) {
+ CertificateCredential certCredential = new CertificateCredential();
+ certCredential.certType = in.readString();
+ certCredential.certSha256FingerPrint = in.createByteArray();
+ return certCredential;
+ }
+
+ @Override
+ public CertificateCredential[] newArray(int size) {
+ return new CertificateCredential[size];
+ }
+ };
+ }
+ public CertificateCredential certCredential = null;
+
+ /**
+ * SIM (Subscriber Identify Module) based credential.
+ * Contains fields under PerProviderSubscription/Credential/SIM subtree.
+ */
+ public static final class SimCredential implements Parcelable {
+ /**
+ * International Mobile device Subscriber Identity.
+ */
+ public String imsi = null;
+
+ /**
+ * EAP (Extensible Authentication Protocol) method type for using SIM credential.
+ * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
+ * for valid values.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ public int eapType = Integer.MIN_VALUE;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof SimCredential)) {
+ return false;
+ }
+
+ SimCredential that = (SimCredential) thatObject;
+ return TextUtils.equals(imsi, that.imsi) &&
+ eapType == that.eapType;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(imsi);
+ dest.writeInt(eapType);
+ }
+
+ public static final Creator<SimCredential> CREATOR =
+ new Creator<SimCredential>() {
+ @Override
+ public SimCredential createFromParcel(Parcel in) {
+ SimCredential simCredential = new SimCredential();
+ simCredential.imsi = in.readString();
+ simCredential.eapType = in.readInt();
+ return simCredential;
+ }
+
+ @Override
+ public SimCredential[] newArray(int size) {
+ return new SimCredential[size];
+ }
+ };
+ }
+ public SimCredential simCredential = null;
+
+ /**
+ * CA (Certificate Authority) X509 certificate.
+ */
+ public X509Certificate caCertificate = null;
+
+ /**
+ * Client side X509 certificate chain.
+ */
+ public X509Certificate[] clientCertificateChain = null;
+
+ /**
+ * Client side private key.
+ */
+ public PrivateKey clientPrivateKey = null;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(realm);
+ dest.writeParcelable(userCredential, flags);
+ dest.writeParcelable(certCredential, flags);
+ dest.writeParcelable(simCredential, flags);
+ ParcelUtil.writeCertificate(dest, caCertificate);
+ ParcelUtil.writeCertificates(dest, clientCertificateChain);
+ ParcelUtil.writePrivateKey(dest, clientPrivateKey);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof Credential)) {
+ return false;
+ }
+
+ Credential that = (Credential) thatObject;
+ return TextUtils.equals(realm, that.realm) &&
+ (userCredential == null ? that.userCredential == null :
+ userCredential.equals(that.userCredential)) &&
+ (certCredential == null ? that.certCredential == null :
+ certCredential.equals(that.certCredential)) &&
+ (simCredential == null ? that.simCredential == null :
+ simCredential.equals(that.simCredential)) &&
+ isX509CertificateEquals(caCertificate, that.caCertificate) &&
+ isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) &&
+ isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
+ }
+
+ public static final Creator<Credential> CREATOR =
+ new Creator<Credential>() {
+ @Override
+ public Credential createFromParcel(Parcel in) {
+ Credential credential = new Credential();
+ credential.realm = in.readString();
+ credential.userCredential = in.readParcelable(null);
+ credential.certCredential = in.readParcelable(null);
+ credential.simCredential = in.readParcelable(null);
+ credential.caCertificate = ParcelUtil.readCertificate(in);
+ credential.clientCertificateChain = ParcelUtil.readCertificates(in);
+ credential.clientPrivateKey = ParcelUtil.readPrivateKey(in);
+ return credential;
+ }
+
+ @Override
+ public Credential[] newArray(int size) {
+ return new Credential[size];
+ }
+ };
+
+ private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
+ if (key1 == null && key2 == null) {
+ return true;
+ }
+
+ /* Return false if only one of them is null */
+ if (key1 == null || key2 == null) {
+ return false;
+ }
+
+ return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) &&
+ Arrays.equals(key1.getEncoded(), key2.getEncoded());
+ }
+
+ private static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) {
+ if (cert1 == null && cert2 == null) {
+ return true;
+ }
+
+ /* Return false if only one of them is null */
+ if (cert1 == null || cert2 == null) {
+ return false;
+ }
+
+ boolean result = false;
+ try {
+ result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded());
+ } catch (CertificateEncodingException e) {
+ /* empty, return false. */
+ }
+ return result;
+ }
+
+ private static boolean isX509CertificatesEquals(X509Certificate[] certs1,
+ X509Certificate[] certs2) {
+ if (certs1 == null && certs2 == null) {
+ return true;
+ }
+
+ /* Return false if only one of them is null */
+ if (certs1 == null || certs2 == null) {
+ return false;
+ }
+
+ if (certs1.length != certs2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < certs1.length; i++) {
+ if (!isX509CertificateEquals(certs1[i], certs2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
new file mode 100644
index 0000000..62d5603
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.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.net.wifi.hotspot2.pps;
+
+parcelable HomeSP;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
new file mode 100644
index 0000000..2acc8be
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -0,0 +1,96 @@
+/**
+ * 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.net.wifi.hotspot2.pps;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+
+/**
+ * Class representing HomeSP subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
+ *
+ * @hide
+ */
+public final class HomeSP implements Parcelable {
+ /**
+ * FQDN (Fully Qualified Domain Name) of this home service provider.
+ */
+ public String fqdn = null;
+
+ /**
+ * Friendly name of this home service provider.
+ */
+ public String friendlyName = null;
+
+ /**
+ * List of Organization Identifiers (OIs) identifying a roaming consortium of
+ * which this provider is a member.
+ */
+ public long[] roamingConsortiumOIs = null;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(fqdn);
+ dest.writeString(friendlyName);
+ dest.writeLongArray(roamingConsortiumOIs);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof HomeSP)) {
+ return false;
+ }
+ HomeSP that = (HomeSP) thatObject;
+
+ return TextUtils.equals(fqdn, that.fqdn) &&
+ TextUtils.equals(friendlyName, that.friendlyName) &&
+ Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
+ }
+
+ public static final Creator<HomeSP> CREATOR =
+ new Creator<HomeSP>() {
+ @Override
+ public HomeSP createFromParcel(Parcel in) {
+ HomeSP homeSp = new HomeSP();
+ homeSp.fqdn = in.readString();
+ homeSp.friendlyName = in.readString();
+ homeSp.roamingConsortiumOIs = in.createLongArray();
+ return homeSp;
+ }
+
+ @Override
+ public HomeSP[] newArray(int size) {
+ return new HomeSP[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
index 56baba9..5485824 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
@@ -23,6 +23,7 @@
import android.net.wifi.nan.IWifiNanEventCallback;
import android.net.wifi.nan.PublishConfig;
import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanCharacteristics;
import android.net.wifi.RttManager;
/**
@@ -36,6 +37,7 @@
void enableUsage();
void disableUsage();
boolean isUsageEnabled();
+ WifiNanCharacteristics getCharacteristics();
// client API
void connect(in IBinder binder, in String callingPackage, in IWifiNanEventCallback callback,
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
index 9151371..30c5bc0 100644
--- a/wifi/java/android/net/wifi/nan/PublishConfig.java
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -182,7 +182,7 @@
*
* @hide
*/
- public void validate() throws IllegalArgumentException {
+ public void assertValid(WifiNanCharacteristics characteristics) throws IllegalArgumentException {
WifiNanUtils.validateServiceName(mServiceName);
if (!LvBufferUtils.isValid(mMatchFilter, 1)) {
@@ -198,6 +198,26 @@
if (mTtlSec < 0) {
throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
}
+
+ if (characteristics != null) {
+ int maxServiceNameLength = characteristics.getMaxServiceNameLength();
+ if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
+ throw new IllegalArgumentException(
+ "Service name longer than supported by device characteristics");
+ }
+ int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
+ if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
+ && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
+ throw new IllegalArgumentException(
+ "Service specific info longer than supported by device characteristics");
+ }
+ int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
+ if (maxMatchFilterLength != 0 && mMatchFilter != null
+ && mMatchFilter.length > maxMatchFilterLength) {
+ throw new IllegalArgumentException(
+ "Match filter longer than supported by device characteristics");
+ }
+ }
}
/**
@@ -257,25 +277,6 @@
}
/**
- * Specify service specific information for the publish session - a simple wrapper
- * of {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])}
- * obtaining the data from a String.
- * <p>
- * Optional. Empty by default.
- *
- * @param serviceSpecificInfoStr The service specific information string
- * to be included (as a byte array) in the publish
- * information.
- *
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceSpecificInfo(@NonNull String serviceSpecificInfoStr) {
- mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
- return this;
- }
-
- /**
* The match filter for a publish session. Used to determine whether a service
* discovery occurred - in addition to relying on the service name.
* <p>
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
index b1dcd8f..ea7b8e4 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -209,7 +209,7 @@
*
* @hide
*/
- public void validate() throws IllegalArgumentException {
+ public void assertValid(WifiNanCharacteristics characteristics) throws IllegalArgumentException {
WifiNanUtils.validateServiceName(mServiceName);
if (!LvBufferUtils.isValid(mMatchFilter, 1)) {
@@ -229,6 +229,26 @@
throw new IllegalArgumentException(
"Invalid matchType - must be MATCH_FIRST_ONLY or MATCH_ALL");
}
+
+ if (characteristics != null) {
+ int maxServiceNameLength = characteristics.getMaxServiceNameLength();
+ if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
+ throw new IllegalArgumentException(
+ "Service name longer than supported by device characteristics");
+ }
+ int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
+ if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
+ && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
+ throw new IllegalArgumentException(
+ "Service specific info longer than supported by device characteristics");
+ }
+ int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
+ if (maxMatchFilterLength != 0 && mMatchFilter != null
+ && mMatchFilter.length > maxMatchFilterLength) {
+ throw new IllegalArgumentException(
+ "Match filter longer than supported by device characteristics");
+ }
+ }
}
/**
@@ -289,25 +309,6 @@
}
/**
- * Specify service specific information for the subscribe session - a simple wrapper
- * of {@link SubscribeConfig.Builder#setServiceSpecificInfo(byte[])}
- * obtaining the data from a String.
- * <p>
- * Optional. Empty by default.
- *
- * @param serviceSpecificInfoStr The service specific information string
- * to be included (as a byte array) in the subscribe
- * information.
- *
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceSpecificInfo(@NonNull String serviceSpecificInfoStr) {
- mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
- return this;
- }
-
- /**
* The match filter for a subscribe session. Used to determine whether a service
* discovery occurred - in addition to relying on the service name.
* <p>
diff --git a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.aidl
similarity index 69%
copy from core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
copy to wifi/java/android/net/wifi/nan/WifiNanCharacteristics.aidl
index fb76e67..e562a00 100644
--- a/core/tests/coretests/src/android/print/mockservice/SettingsActivity.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.aidl
@@ -14,15 +14,6 @@
* limitations under the License.
*/
-package android.print.mockservice;
+package android.net.wifi.nan;
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
+parcelable WifiNanCharacteristics;
diff --git a/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.java b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.java
new file mode 100644
index 0000000..f43ed4d
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.java
@@ -0,0 +1,103 @@
+/*
+ * 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.net.wifi.nan;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The characteristics of the Wi-Fi NAN implementation.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanCharacteristics implements Parcelable {
+ /** @hide */
+ public static final String KEY_MAX_SERVICE_NAME_LENGTH = "key_max_service_name_length";
+ /** @hide */
+ public static final String KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH =
+ "key_max_service_specific_info_length";
+ /** @hide */
+ public static final String KEY_MAX_MATCH_FILTER_LENGTH = "key_max_match_filter_length";
+
+ private Bundle mCharacteristics = new Bundle();
+
+ /** @hide : should not be created by apps */
+ public WifiNanCharacteristics(Bundle characteristics) {
+ mCharacteristics = characteristics;
+ }
+
+ /**
+ * Returns the maximum string length that can be used to specify a NAN service name. Restricts
+ * the parameters of the {@link PublishConfig.Builder#setServiceName(String)} and
+ * {@link SubscribeConfig.Builder#setServiceName(String)}.
+ *
+ * @return A positive integer, maximum string length of NAN service name.
+ */
+ public int getMaxServiceNameLength() {
+ return mCharacteristics.getInt(KEY_MAX_SERVICE_NAME_LENGTH);
+ }
+
+ /**
+ * Returns the maximum length of byte array that can be used to specify a NAN service specific
+ * information field: the arbitrary load used in discovery or the message length of NAN
+ * message exchange. Restricts the parameters of the
+ * {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])},
+ * {@link SubscribeConfig.Builder#setServiceSpecificInfo(byte[])}, and
+ * {@link WifiNanDiscoveryBaseSession#sendMessage(Object, int, byte[])} variants.
+ *
+ * @return A positive integer, maximum length of byte array for NAN messaging.
+ */
+ public int getMaxServiceSpecificInfoLength() {
+ return mCharacteristics.getInt(KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH);
+ }
+
+ /**
+ * Returns the maximum length of byte array that can be used to specify a NAN match filter.
+ * Restricts the parameters of the {@link PublishConfig.Builder#setMatchFilter(byte[])} and
+ * {@link SubscribeConfig.Builder#setMatchFilter(byte[])}.
+ *
+ * @return A positive integer, maximum legngth of byte array for NAN discovery match filter.
+ */
+ public int getMaxMatchFilterLength() {
+ return mCharacteristics.getInt(KEY_MAX_MATCH_FILTER_LENGTH);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mCharacteristics);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<WifiNanCharacteristics> CREATOR =
+ new Creator<WifiNanCharacteristics>() {
+ @Override
+ public WifiNanCharacteristics createFromParcel(Parcel in) {
+ WifiNanCharacteristics c = new WifiNanCharacteristics(in.readBundle());
+ return c;
+ }
+
+ @Override
+ public WifiNanCharacteristics[] newArray(int size) {
+ return new WifiNanCharacteristics[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
index 705ba4a..002b953 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanManager.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -201,6 +201,8 @@
* Use the {@link #isAvailable()} to query the current status.
* This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
* the broadcast to check the current state of Wi-Fi NAN.
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_WIFI_NAN_STATE_CHANGED =
@@ -291,6 +293,20 @@
}
/**
+ * Returns the characteristics of the Wi-Fi NAN interface: a set of parameters which specify
+ * limitations on configurations, e.g. the maximum service name length.
+ *
+ * @return An object specifying configuration limitations of NAN.
+ */
+ public WifiNanCharacteristics getCharacteristics() {
+ try {
+ return mService.getCharacteristics();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Attach to the Wi-Fi NAN service - enabling the application to create discovery sessions or
* create connections to peers. The device will attach to an existing cluster if it can find
* one or create a new cluster (if it is the first to enable NAN in its vicinity). Results
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSession.java b/wifi/java/android/net/wifi/nan/WifiNanSession.java
index 5fb2c06..df5e3c1 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSession.java
@@ -113,6 +113,8 @@
* An application must use the {@link WifiNanDiscoveryBaseSession#destroy()} to
* terminate the publish discovery session once it isn't needed. This will free
* resources as well terminate any on-air transmissions.
+ * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ * permission to start a publish discovery session.
*
* @param handler The Handler on whose thread to execute the callbacks of the {@code
* callback} object. If a null is provided then the application's main thread will be used.
@@ -156,6 +158,8 @@
* An application must use the {@link WifiNanDiscoveryBaseSession#destroy()} to
* terminate the subscribe discovery session once it isn't needed. This will free
* resources as well terminate any on-air transmissions.
+ * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ * permission to start a subscribe discovery session.
*
* @param handler The Handler on whose thread to execute the callbacks of the {@code
* callback} object. If a null is provided then the application's main thread will be used.
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
new file mode 100644
index 0000000..5850fee
--- /dev/null
+++ b/wifi/tests/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Make test APK
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# This list is generated from the java source files in this module
+# The list is a comma separated list of class names with * matching zero or more characters.
+# Example:
+# Input files: src/com/android/server/wifi/Test.java src/com/android/server/wifi/AnotherTest.java
+# Generated exclude list: com.android.server.wifi.Test*,com.android.server.wifi.AnotherTest*
+
+# Filter all src files to just java files
+local_java_files := $(filter %.java,$(LOCAL_SRC_FILES))
+# Transform java file names into full class names.
+# This only works if the class name matches the file name and the directory structure
+# matches the package.
+local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
+# Utility variables to allow replacing a space with a comma
+comma:= ,
+empty:=
+space:= $(empty) $(empty)
+# Convert class name list to jacoco exclude list
+# This appends a * to all classes and replace the space separators with commas.
+# These patterns will match all classes in this module and their inner classes.
+jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
+
+jacoco_include := android.net.wifi.*
+
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ frameworks-base-testutils \
+
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+
+LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
+
+include $(BUILD_PACKAGE)
diff --git a/wifi/tests/AndroidManifest.xml b/wifi/tests/AndroidManifest.xml
new file mode 100644
index 0000000..4eaca2b
--- /dev/null
+++ b/wifi/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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="android.net.wifi.test">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:label="WifiTestDummyLabel"
+ android:name="WifiTestDummyName">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.net.wifi.test"
+ android:label="Frameworks Wifi API Tests">
+ </instrumentation>
+
+</manifest>
diff --git a/wifi/tests/README.md b/wifi/tests/README.md
new file mode 100644
index 0000000..b418abd
--- /dev/null
+++ b/wifi/tests/README.md
@@ -0,0 +1,50 @@
+# Wifi Unit Tests
+This package contains unit tests for the android wifi framework APIs based on the
+[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
+The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
+libraries.
+
+## Running Tests
+The easiest way to run tests is simply run
+
+```
+frameworks/base/wifi/tests/runtests.sh
+```
+
+`runtests.sh` will build the test project and all of its dependencies and push the APK to the
+connected device. It will then run the tests on the device.
+
+To pick up changes in framework/base, you will need to:
+1. rebuild the framework library 'make -j32'
+2. sync over the updated library to the device 'adb sync'
+3. restart framework on the device 'adb shell stop' then 'adb shell start'
+
+To enable syncing data to the device for first time after clean reflash:
+1. adb disable-verity
+2. adb reboot
+3. adb remount
+
+See below for a few example of options to limit which tests are run.
+See the
+[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
+for more details on the supported options.
+
+```
+runtests.sh -e package android.net.wifi
+runtests.sh -e class android.net.wifi.WifiScannerTest
+```
+
+If you manually build and push the test APK to the device you can run tests using
+
+```
+adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+```
+
+## Adding Tests
+Tests can be added by adding classes to the src directory. JUnit4 style test cases can
+be written by simply annotating test methods with `org.junit.Test`.
+
+## Debugging Tests
+If you are trying to debug why tests are not doing what you expected, you can add android log
+statements and use logcat to view them. The beginning and end of every tests is automatically logged
+with the tag `TestRunner`.
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
new file mode 100644
index 0000000..53d38ad
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -0,0 +1,80 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/PerProviderSubscription_DuplicateHomeSP.xml b/wifi/tests/assets/pps/PerProviderSubscription_DuplicateHomeSP.xml
new file mode 100644
index 0000000..e13eb2a
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription_DuplicateHomeSP.xml
@@ -0,0 +1,95 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/PerProviderSubscription_DuplicateValue.xml b/wifi/tests/assets/pps/PerProviderSubscription_DuplicateValue.xml
new file mode 100644
index 0000000..8719ffa
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription_DuplicateValue.xml
@@ -0,0 +1,81 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/PerProviderSubscription_InvalidName.xml b/wifi/tests/assets/pps/PerProviderSubscription_InvalidName.xml
new file mode 100644
index 0000000..c761237
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription_InvalidName.xml
@@ -0,0 +1,80 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/PerProviderSubscription_InvalidNode.xml b/wifi/tests/assets/pps/PerProviderSubscription_InvalidNode.xml
new file mode 100644
index 0000000..6b807af
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription_InvalidNode.xml
@@ -0,0 +1,84 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ <Node>
+ <NodeName>InvalidNode</NodeName>
+ <Value>Test</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/PerProviderSubscription_MissingName.xml b/wifi/tests/assets/pps/PerProviderSubscription_MissingName.xml
new file mode 100644
index 0000000..ed06b47
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription_MissingName.xml
@@ -0,0 +1,79 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ <Value>Century House</Value>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/PerProviderSubscription_MissingValue.xml b/wifi/tests/assets/pps/PerProviderSubscription_MissingValue.xml
new file mode 100644
index 0000000..f7e35dd
--- /dev/null
+++ b/wifi/tests/assets/pps/PerProviderSubscription_MissingValue.xml
@@ -0,0 +1,79 @@
+<MgmtTree xmlns="syncml:dmddf1.2">
+ <VerDTD>1.2</VerDTD>
+ <Node>
+ <NodeName>PerProviderSubscription</NodeName>
+ <RTProperties>
+ <Type>
+ <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
+ </Type>
+ </RTProperties>
+ <Node>
+ <NodeName>i001</NodeName>
+ <Node>
+ <NodeName>HomeSP</NodeName>
+ <Node>
+ <NodeName>FriendlyName</NodeName>
+ </Node>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>mi6.co.uk</Value>
+ </Node>
+ <Node>
+ <NodeName>RoamingConsortiumOI</NodeName>
+ <Value>112233,445566</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>Credential</NodeName>
+ <Node>
+ <NodeName>Realm</NodeName>
+ <Value>shaken.stirred.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>james</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>Ym9uZDAwNw==</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPMethod</NodeName>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>21</Value>
+ </Node>
+ <Node>
+ <NodeName>InnerMethod</NodeName>
+ <Value>MS-CHAP-V2</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>DigitalCertificate</NodeName>
+ <Node>
+ <NodeName>CertificateType</NodeName>
+ <Value>x509v3</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256FingerPrint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SIM</NodeName>
+ <Node>
+ <NodeName>IMSI</NodeName>
+ <Value>imsi</Value>
+ </Node>
+ <Node>
+ <NodeName>EAPType</NodeName>
+ <Value>24</Value>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+ </Node>
+</MgmtTree>
diff --git a/wifi/tests/assets/pps/README.txt b/wifi/tests/assets/pps/README.txt
new file mode 100644
index 0000000..369c0a9
--- /dev/null
+++ b/wifi/tests/assets/pps/README.txt
@@ -0,0 +1,7 @@
+PerProviderSubscription.xml - valid PPS XML file
+PerProviderSubscription_DuplicateHomeSP.xml - containing multiple HomeSP node
+PerProviderSubscription_DuplicateValue.xml - FriendlyName node contains multiple Value
+PerProviderSubscription_MissingValue.xml - FriendlyName node is missing Value
+PerProviderSubscription_MissingName.xml - HomeSP node is missing NodeName
+PerProviderSubscription_InvalidNode.xml - FQDN node contains both Value and a child node
+PerProviderSubscription_InvalidName.xml - FriendlyName node have a typo in its name
diff --git a/wifi/tests/runtests.sh b/wifi/tests/runtests.sh
new file mode 100755
index 0000000..ebcc2a2
--- /dev/null
+++ b/wifi/tests/runtests.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+ echo "You need to source and lunch before you can use this script"
+ exit 1
+fi
+
+echo "Running tests"
+
+set -e # fail early
+
+echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/wifi/tests"
+# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
+# caller.
+make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-wifi-tests
+
+set -x # print commands
+
+adb root
+adb wait-for-device
+
+adb install -r -g "$OUT/data/app/FrameworksWifiApiTests/FrameworksWifiApiTests.apk"
+
+adb shell am instrument -w "$@" 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
diff --git a/wifi/tests/src/android/net/wifi/FakeKeys.java b/wifi/tests/src/android/net/wifi/FakeKeys.java
new file mode 100644
index 0000000..c0d60c3
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/FakeKeys.java
@@ -0,0 +1,237 @@
+/*
+ * 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.net.wifi;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * A class containing test certificates and private keys.
+ */
+public class FakeKeys {
+ private static final String CA_CERT0_STRING = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDKDCCAhCgAwIBAgIJAILlFdwzLVurMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
+ "BAMTB0VBUCBDQTEwHhcNMTYwMTEyMTE1MDE1WhcNMjYwMTA5MTE1MDE1WjASMRAw\n" +
+ "DgYDVQQDEwdFQVAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
+ "znAPUz26Msae4ws43czR41/J2QtrSIZUKmVUsVumDbYHrPNvTXKSMXAcewORDQYX\n" +
+ "RqvHvpn8CscB1+oGXZvHwxj4zV0WKoK2zeXkau3vcyl3HIKupJfq2TEACefVjj0t\n" +
+ "JW+X35PGWp9/H5zIUNVNVjS7Ums84IvKhRB8512PB9UyHagXYVX5GWpAcVpyfrlR\n" +
+ "FI9Qdhh+Pbk0uyktdbf/CdfgHOoebrTtwRljM0oDtX+2Cv6j0wBK7hD8pPvf1+uy\n" +
+ "GzczigAU/4Kw7eZqydf9B+5RupR+IZipX41xEiIrKRwqi517WWzXcjaG2cNbf451\n" +
+ "xpH5PnV3i1tq04jMGQUzFwIDAQABo4GAMH4wHQYDVR0OBBYEFIwX4vs8BiBcScod\n" +
+ "5noZHRM8E4+iMEIGA1UdIwQ7MDmAFIwX4vs8BiBcScod5noZHRM8E4+ioRakFDAS\n" +
+ "MRAwDgYDVQQDEwdFQVAgQ0ExggkAguUV3DMtW6swDAYDVR0TBAUwAwEB/zALBgNV\n" +
+ "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFfQqOTA7Rv7K+luQ7pnas4BYwHE\n" +
+ "9GEP/uohv6KOy0TGQFbrRTjFoLVNB9BZ1ymMDZ0/TIwIUc7wi7a8t5mEqYH153wW\n" +
+ "aWooiSjyLLhuI4sNrNCOtisdBq2r2MFXt6h0mAQYOPv8R8K7/fgSxGFqzhyNmmVL\n" +
+ "1qBJldx34SpwsTALQVPb4hGwJzZfr1PcpEQx6xMnTl8xEWZE3Ms99uaUxbQqIwRu\n" +
+ "LgAOkNCmY2m89VhzaHJ1uV85AdM/tD+Ysmlnnjt9LRCejbBipjIGjOXrg1JP+lxV\n" +
+ "muM4vH+P/mlmxsPPz0d65b+EGmJZpoLkO/tdNNvCYzjJpTEWpEsO6NMhKYo=\n" +
+ "-----END CERTIFICATE-----\n";
+ public static final X509Certificate CA_CERT0 = loadCertificate(CA_CERT0_STRING);
+
+ private static final String CA_CERT1_STRING = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDKDCCAhCgAwIBAgIJAOM5SzKO2pzCMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
+ "BAMTB0VBUCBDQTAwHhcNMTYwMTEyMDAxMDQ3WhcNMjYwMTA5MDAxMDQ3WjASMRAw\n" +
+ "DgYDVQQDEwdFQVAgQ0EwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
+ "89ug+IEKVQXnJGKg5g4uVHg6J/8iRUxR5k2eH5o03hrJNMfN2D+cBe/wCiZcnWbI\n" +
+ "GbGZACWm2nQth2wy9Zgm2LOd3b4ocrHYls3XLq6Qb5Dd7a0JKU7pdGufiNVEkrmF\n" +
+ "EB+N64wgwH4COTvCiN4erp5kyJwkfqAl2xLkZo0C464c9XoyQOXbmYD9A8v10wZu\n" +
+ "jyNsEo7Nr2USyw+qhjWSbFbEirP77Tvx+7pJQJwdtk1V9Tn73T2dGF2WHYejei9S\n" +
+ "mcWpdIUqsu9etYH+zDmtu7I1xlkwiaVsNr2+D+qaCJyOYqrDTKVNK5nmbBPXDWZc\n" +
+ "NoDbTOoqquX7xONpq9M6jQIDAQABo4GAMH4wHQYDVR0OBBYEFAZ3A2S4qJZZwuNY\n" +
+ "wkJ6mAdc0gVdMEIGA1UdIwQ7MDmAFAZ3A2S4qJZZwuNYwkJ6mAdc0gVdoRakFDAS\n" +
+ "MRAwDgYDVQQDEwdFQVAgQ0EwggkA4zlLMo7anMIwDAYDVR0TBAUwAwEB/zALBgNV\n" +
+ "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAHmdMwEhtys4d0E+t7owBmoVR+lU\n" +
+ "hMCcRtWs8YKX5WIM2kTweT0h/O1xwE1mWmRv/IbDAEb8od4BjAQLhIcolStr2JaO\n" +
+ "9ZzyxjOnNzqeErh/1DHDbb/moPpqfeJ8YiEz7nH/YU56Q8iCPO7TsgS0sNNE7PfN\n" +
+ "IUsBW0yHRgpQ4OxWmiZG2YZWiECRzAC0ecPzo59N5iH4vLQIMTMYquiDeMPQnn1e\n" +
+ "NDGxG8gCtDKIaS6tMg3a28MvWB094pr2ETou8O1C8Ji0Y4hE8QJmSdT7I4+GZjgW\n" +
+ "g94DZ5RiL7sdp3vC48CXOmeT61YBIvhGUsE1rPhXqkpqQ3Z3C4TFF0jXZZc=\n" +
+ "-----END CERTIFICATE-----\n";
+ public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
+
+ private static final String CLIENT_CERT_STR = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIE/DCCAuQCAQEwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxCzAJBgNV\n" +
+ "BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0aW5n\n" +
+ "MB4XDTE2MDkzMDIwNTQyOFoXDTE3MDkzMDIwNTQyOFowRDELMAkGA1UEBhMCVVMx\n" +
+ "CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdU\n" +
+ "ZXN0aW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpmcbuaeHfnJ\n" +
+ "k+2QNvxmdVFTawyFMNk0USCq5sexscwmxbewG/Rb8YnixwJWS44v2XkSujB67z5C\n" +
+ "s2qudFEhRXKdEuC6idbAuA97KjipHh0AAniWMsyv61fvbgsUC0b0canx3LiDq81p\n" +
+ "y28NNGmAvoazLZUZ4AhBRiwYZY6FKk723gmZoGbEIeG7J1dlXPusc1662rIjz4eU\n" +
+ "zlmmlvqyHfNqnNk8L14Vug6Xh+lOEGN85xhu1YHAEKGrS89kZxs5rum/cZU8KH2V\n" +
+ "v6eKnY03kxjiVLQtnLpm/7VUEoCMGHyruRj+p3my4+DgqMsmsH52RZCBsjyGlpbU\n" +
+ "NOwOTIX6xh+Rqloduz4AnrMYYIiIw2s8g+2zJM7VbcVKx0fGS26BKdrxgrXWfmNE\n" +
+ "nR0/REQ5AxDGw0jfTUvtdTkXAf+K4MDjcNLEZ+MA4rHfAfQWZtUR5BkHCQYxNpJk\n" +
+ "pA0gyk+BpKdC4WdzI14NSWsu5sRCmBCFqH6BTOSEq/V1cNorBxNwLSSTwFFqUDqx\n" +
+ "Y5nQLXygkJf9WHZWtSKeSjtOYgilz7UKzC2s3CsjmIyGFe+SwpuHJnuE4Uc8Z5Cb\n" +
+ "bjNGHPzqL6XnmzZHJp7RF8kBdKdjGC7dCUltzOfICZeKlzOOq+Kw42T/nXjuXvpb\n" +
+ "nkXNxg741Nwd6RecykXJbseFwm3EYxkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEA\n" +
+ "Ga1mGwI9aXkL2fTPXO9YkAPzoGeX8aeuVYSQaSkNq+5vnogYCyAt3YDHjRG+ewTT\n" +
+ "WbnPA991xRAPac+biJeXWmwvgGj0YuT7e79phAiGkTTnbAjFHGfYnBy/tI/v7btO\n" +
+ "hRNElA5yTJ1m2fVbBEKXzMR83jrT9iyI+YLRN86zUZIaC86xxSbqnrdWN2jOK6MX\n" +
+ "dS8Arp9tPQjC/4gW+2Ilxv68jiYh+5auWHQZVjppWVY//iu4mAbkq1pTwQEhZ8F8\n" +
+ "Zrmh9DHh60hLFcfSuhIAwf/NMzppwdkjy1ruKVrpijhGKGp4OWu8nvOUgHSzxc7F\n" +
+ "PwpVZ5N2Ku4L8MLO6BG2VasRJK7l17TzDXlfLZHJjkuryOFxVaQKt8ZNFgTOaCXS\n" +
+ "E+gpTLksKU7riYckoiP4+H1sn9qcis0e8s4o/uf1UVc8GSdDw61ReGM5oZEDm1u8\n" +
+ "H9x20QU6igLqzyBpqvCKv7JNgU1uB2PAODHH78zJiUfnKd1y+o+J1iWzaGj3EFji\n" +
+ "T8AXksbTP733FeFXfggXju2dyBH+Z1S5BBTEOd1brWgXlHSAZGm97MKZ94r6/tkX\n" +
+ "qfv3fCos0DKz0oV7qBxYS8wiYhzrRVxG6ITAoH8uuUVVQaZF+G4nJ2jEqNbfuKyX\n" +
+ "ATQsVNjNNlDA0J33GobPMjT326wa4YAWMx8PI5PJZ3g=\n" +
+ "-----END CERTIFICATE-----\n";
+ public static final X509Certificate CLIENT_CERT = loadCertificate(CLIENT_CERT_STR);
+
+ private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
+ (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
+ (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+ (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+ (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+ (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
+ (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+ (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
+ (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
+ (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
+ (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
+ (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
+ (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
+ (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
+ (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
+ (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
+ (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
+ (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
+ (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
+ (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
+ (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
+ (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
+ (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
+ (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
+ (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
+ (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
+ (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
+ (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
+ (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
+ (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
+ (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
+ (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
+ (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
+ (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
+ (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
+ (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
+ (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
+ (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
+ (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
+ (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
+ (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
+ (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
+ (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
+ (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
+ (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
+ (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
+ (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
+ (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
+ (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
+ (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
+ (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
+ (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
+ (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
+ (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
+ (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
+ (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
+ (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
+ (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
+ (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
+ (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
+ (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
+ (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
+ (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
+ (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
+ (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
+ (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
+ (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
+ (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
+ (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
+ (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
+ (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
+ (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
+ (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
+ (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
+ (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
+ (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
+ (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
+ (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
+ (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
+ (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
+ (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
+ (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
+ (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
+ (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
+ (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
+ (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
+ (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
+ (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
+ (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
+ (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
+ (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
+ (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
+ (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
+ (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
+ (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
+ (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
+ (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
+ (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
+ (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
+ (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
+ (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
+ (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
+ (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
+ (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
+ (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
+ (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
+ (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
+ };
+ public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
+
+ private static X509Certificate loadCertificate(String blob) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
+
+ return (X509Certificate) certFactory.generateCertificate(stream);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static PrivateKey loadPrivateRSAKey(byte[] fakeKey) {
+ try {
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ return null;
+ }
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/ParcelUtilTest.java b/wifi/tests/src/android/net/wifi/ParcelUtilTest.java
new file mode 100644
index 0000000..6787594
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/ParcelUtilTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.net.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * Unit tests for {@link android.net.wifi.ParcelUtil}.
+ */
+@SmallTest
+public class ParcelUtilTest {
+ private Parcel mParcel;
+
+ @Before
+ public void setUp() throws Exception {
+ mParcel = Parcel.obtain();
+ }
+
+ @Test
+ public void readWriteNullPrivateKey() throws Exception {
+ ParcelUtil.writePrivateKey(mParcel, null);
+
+ mParcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ PrivateKey readKey = ParcelUtil.readPrivateKey(mParcel);
+ assertNull(readKey);
+ }
+
+ @Test
+ public void readWriteValidPrivateKey() throws Exception {
+ PrivateKey writeKey = FakeKeys.RSA_KEY1;
+ ParcelUtil.writePrivateKey(mParcel, writeKey);
+
+ mParcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ PrivateKey readKey = ParcelUtil.readPrivateKey(mParcel);
+ assertNotNull(readKey);
+ assertEquals(writeKey.getAlgorithm(), readKey.getAlgorithm());
+ assertArrayEquals(writeKey.getEncoded(), readKey.getEncoded());
+ }
+
+ @Test
+ public void readWriteNullCertificate() throws Exception {
+ ParcelUtil.writeCertificate(mParcel, null);
+
+ mParcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ X509Certificate readCert = ParcelUtil.readCertificate(mParcel);
+ assertNull(readCert);
+ }
+
+ @Test
+ public void readWriteValidCertificate() throws Exception {
+ X509Certificate writeCert = FakeKeys.CA_CERT1;
+ ParcelUtil.writeCertificate(mParcel, writeCert);
+
+ mParcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ X509Certificate readCert = ParcelUtil.readCertificate(mParcel);
+ assertNotNull(readCert);
+ assertArrayEquals(writeCert.getEncoded(), readCert.getEncoded());
+ }
+
+ @Test
+ public void readWriteNullCertificates() throws Exception {
+ ParcelUtil.writeCertificates(mParcel, null);
+
+ mParcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ X509Certificate[] readCerts = ParcelUtil.readCertificates(mParcel);
+ assertNull(readCerts);
+ }
+
+ @Test
+ public void readWriteValidCertificates() throws Exception {
+ X509Certificate[] writeCerts = new X509Certificate[2];
+ writeCerts[0] = FakeKeys.CA_CERT0;
+ writeCerts[1] = FakeKeys.CA_CERT1;
+ ParcelUtil.writeCertificates(mParcel, writeCerts);
+
+ mParcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ X509Certificate[] readCerts = ParcelUtil.readCertificates(mParcel);
+ assertNotNull(readCerts);
+ assertEquals(writeCerts.length, readCerts.length);
+ for (int i = 0; i < writeCerts.length; i++) {
+ assertNotNull(readCerts[i]);
+ assertArrayEquals(writeCerts[i].getEncoded(), readCerts[i].getEncoded());
+ }
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
new file mode 100644
index 0000000..0e503d5
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.net.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.WifiEnterpriseConfig.Eap;
+import android.net.wifi.WifiEnterpriseConfig.Phase2;
+import android.os.Parcel;
+import android.security.Credentials;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiEnterpriseConfig}.
+ */
+@SmallTest
+public class WifiEnterpriseConfigTest {
+ // Maintain a ground truth of the keystore uri prefix which is expected by wpa_supplicant.
+ public static final String KEYSTORE_URI = "keystore://";
+ public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
+ public static final String KEYSTORES_URI = "keystores://";
+
+ private WifiEnterpriseConfig mEnterpriseConfig;
+
+ @Before
+ public void setUp() throws Exception {
+ mEnterpriseConfig = new WifiEnterpriseConfig();
+ }
+
+ @Test
+ public void testGetEmptyCaCertificate() {
+ // A newly-constructed WifiEnterpriseConfig object should have no CA certificate.
+ assertNull(mEnterpriseConfig.getCaCertificate());
+ assertNull(mEnterpriseConfig.getCaCertificates());
+ // Setting CA certificate to null explicitly.
+ mEnterpriseConfig.setCaCertificate(null);
+ assertNull(mEnterpriseConfig.getCaCertificate());
+ // Setting CA certificate to null using setCaCertificates().
+ mEnterpriseConfig.setCaCertificates(null);
+ assertNull(mEnterpriseConfig.getCaCertificates());
+ // Setting CA certificate to zero-length array.
+ mEnterpriseConfig.setCaCertificates(new X509Certificate[0]);
+ assertNull(mEnterpriseConfig.getCaCertificates());
+ }
+
+ @Test
+ public void testSetGetSingleCaCertificate() {
+ X509Certificate cert0 = FakeKeys.CA_CERT0;
+ mEnterpriseConfig.setCaCertificate(cert0);
+ assertEquals(mEnterpriseConfig.getCaCertificate(), cert0);
+ }
+
+ @Test
+ public void testSetGetMultipleCaCertificates() {
+ X509Certificate cert0 = FakeKeys.CA_CERT0;
+ X509Certificate cert1 = FakeKeys.CA_CERT1;
+ mEnterpriseConfig.setCaCertificates(new X509Certificate[] {cert0, cert1});
+ X509Certificate[] result = mEnterpriseConfig.getCaCertificates();
+ assertEquals(result.length, 2);
+ assertTrue(result[0] == cert0 && result[1] == cert1);
+ }
+
+ @Test
+ public void testSaveSingleCaCertificateAlias() {
+ final String alias = "single_alias 0";
+ mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
+ assertEquals(getCaCertField(), CA_CERT_PREFIX + alias);
+ }
+
+ @Test
+ public void testLoadSingleCaCertificateAlias() {
+ final String alias = "single_alias 1";
+ setCaCertField(CA_CERT_PREFIX + alias);
+ String[] aliases = mEnterpriseConfig.getCaCertificateAliases();
+ assertEquals(aliases.length, 1);
+ assertEquals(aliases[0], alias);
+ }
+
+ @Test
+ public void testSaveMultipleCaCertificates() {
+ final String alias0 = "single_alias 0";
+ final String alias1 = "single_alias 1";
+ mEnterpriseConfig.setCaCertificateAliases(new String[] {alias0, alias1});
+ assertEquals(getCaCertField(), String.format("%s%s %s",
+ KEYSTORES_URI,
+ WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias0),
+ WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias1)));
+ }
+
+ @Test
+ public void testLoadMultipleCaCertificates() {
+ final String alias0 = "single_alias 0";
+ final String alias1 = "single_alias 1";
+ setCaCertField(String.format("%s%s %s",
+ KEYSTORES_URI,
+ WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias0),
+ WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias1)));
+ String[] aliases = mEnterpriseConfig.getCaCertificateAliases();
+ assertEquals(aliases.length, 2);
+ assertEquals(aliases[0], alias0);
+ assertEquals(aliases[1], alias1);
+ }
+
+ private String getCaCertField() {
+ return mEnterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY);
+ }
+
+ private void setCaCertField(String value) {
+ mEnterpriseConfig.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, value);
+ }
+
+ // Retrieves the value for a specific key supplied to wpa_supplicant.
+ private class SupplicantConfigExtractor implements WifiEnterpriseConfig.SupplicantSaver {
+ private String mValue = null;
+ private String mKey;
+
+ SupplicantConfigExtractor(String key) {
+ mKey = key;
+ }
+
+ @Override
+ public boolean saveValue(String key, String value) {
+ if (key.equals(mKey)) {
+ mValue = value;
+ }
+ return true;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+ }
+
+ private String getSupplicantEapMethod() {
+ SupplicantConfigExtractor entryExtractor = new SupplicantConfigExtractor(
+ WifiEnterpriseConfig.EAP_KEY);
+ mEnterpriseConfig.saveToSupplicant(entryExtractor);
+ return entryExtractor.getValue();
+ }
+
+ private String getSupplicantPhase2Method() {
+ SupplicantConfigExtractor entryExtractor = new SupplicantConfigExtractor(
+ WifiEnterpriseConfig.PHASE2_KEY);
+ mEnterpriseConfig.saveToSupplicant(entryExtractor);
+ return entryExtractor.getValue();
+ }
+
+ /** Verifies the default value for EAP outer and inner methods */
+ @Test
+ public void eapInnerDefault() {
+ assertEquals(null, getSupplicantEapMethod());
+ assertEquals(null, getSupplicantPhase2Method());
+ }
+
+ /** Verifies that the EAP inner method is reset when we switch to TLS */
+ @Test
+ public void eapPhase2MethodForTls() {
+ // Initially select an EAP method that supports an phase2.
+ mEnterpriseConfig.setEapMethod(Eap.PEAP);
+ mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
+ assertEquals("PEAP", getSupplicantEapMethod());
+ assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
+
+ // Change the EAP method to another type which supports a phase2.
+ mEnterpriseConfig.setEapMethod(Eap.TTLS);
+ assertEquals("TTLS", getSupplicantEapMethod());
+ assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
+
+ // Change the EAP method to TLS which does not support a phase2.
+ mEnterpriseConfig.setEapMethod(Eap.TLS);
+ assertEquals(null, getSupplicantPhase2Method());
+ }
+
+ /** Verfies that the EAP inner method is reset when we switch phase2 to NONE */
+ @Test
+ public void eapPhase2None() {
+ // Initially select an EAP method that supports an phase2.
+ mEnterpriseConfig.setEapMethod(Eap.PEAP);
+ mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
+ assertEquals("PEAP", getSupplicantEapMethod());
+ assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
+
+ // Change the phase2 method to NONE and ensure the value is cleared.
+ mEnterpriseConfig.setPhase2Method(Phase2.NONE);
+ assertEquals(null, getSupplicantPhase2Method());
+ }
+
+ /** Verfies that the correct "autheap" parameter is supplied for TTLS/GTC. */
+ @Test
+ public void peapGtcToTtls() {
+ mEnterpriseConfig.setEapMethod(Eap.PEAP);
+ mEnterpriseConfig.setPhase2Method(Phase2.GTC);
+ assertEquals("PEAP", getSupplicantEapMethod());
+ assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
+
+ mEnterpriseConfig.setEapMethod(Eap.TTLS);
+ assertEquals("TTLS", getSupplicantEapMethod());
+ assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
+ }
+
+ /** Verfies that the correct "auth" parameter is supplied for PEAP/GTC. */
+ @Test
+ public void ttlsGtcToPeap() {
+ mEnterpriseConfig.setEapMethod(Eap.TTLS);
+ mEnterpriseConfig.setPhase2Method(Phase2.GTC);
+ assertEquals("TTLS", getSupplicantEapMethod());
+ assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
+
+ mEnterpriseConfig.setEapMethod(Eap.PEAP);
+ assertEquals("PEAP", getSupplicantEapMethod());
+ assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
+ }
+
+ /** Verfies that the copy constructor preseves the inner method information. */
+ @Test
+ public void copyConstructor() {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setEapMethod(Eap.TTLS);
+ enterpriseConfig.setPhase2Method(Phase2.GTC);
+ mEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+ assertEquals("TTLS", getSupplicantEapMethod());
+ assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
+ }
+
+ /** Verfies that parceling a WifiEnterpriseConfig preseves method information. */
+ @Test
+ public void parcelConstructor() {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setEapMethod(Eap.TTLS);
+ enterpriseConfig.setPhase2Method(Phase2.GTC);
+ Parcel parcel = Parcel.obtain();
+ enterpriseConfig.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Allow parcel to be read from the beginning.
+ mEnterpriseConfig = WifiEnterpriseConfig.CREATOR.createFromParcel(parcel);
+ assertEquals("TTLS", getSupplicantEapMethod());
+ assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
+ }
+
+ /**
+ * Verifies that parceling a WifiEnterpriseConfig preserves the key
+ * and certificates information.
+ */
+ @Test
+ public void parcelConfigWithKeyAndCerts() throws Exception {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+ X509Certificate[] caCerts = new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1};
+ enterpriseConfig.setClientKeyEntry(clientKey, clientCert);
+ enterpriseConfig.setCaCertificates(caCerts);
+ Parcel parcel = Parcel.obtain();
+ enterpriseConfig.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Allow parcel to be read from the beginning.
+ mEnterpriseConfig = WifiEnterpriseConfig.CREATOR.createFromParcel(parcel);
+ PrivateKey actualClientKey = mEnterpriseConfig.getClientPrivateKey();
+ X509Certificate actualClientCert = mEnterpriseConfig.getClientCertificate();
+ X509Certificate[] actualCaCerts = mEnterpriseConfig.getCaCertificates();
+
+ /* Verify client private key. */
+ assertNotNull(actualClientKey);
+ assertEquals(clientKey.getAlgorithm(), actualClientKey.getAlgorithm());
+ assertArrayEquals(clientKey.getEncoded(), actualClientKey.getEncoded());
+
+ /* Verify client certificate. */
+ assertNotNull(actualClientCert);
+ assertArrayEquals(clientCert.getEncoded(), actualClientCert.getEncoded());
+
+ /* Verify CA certificates. */
+ assertNotNull(actualCaCerts);
+ assertEquals(caCerts.length, actualCaCerts.length);
+ for (int i = 0; i < caCerts.length; i++) {
+ assertNotNull(actualCaCerts[i]);
+ assertArrayEquals(caCerts[i].getEncoded(), actualCaCerts[i].getEncoded());
+ }
+ }
+
+ /** Verifies proper operation of the getKeyId() method. */
+ @Test
+ public void getKeyId() {
+ assertEquals("NULL", mEnterpriseConfig.getKeyId(null));
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setEapMethod(Eap.TTLS);
+ enterpriseConfig.setPhase2Method(Phase2.GTC);
+ assertEquals("TTLS_GTC", mEnterpriseConfig.getKeyId(enterpriseConfig));
+ mEnterpriseConfig.setEapMethod(Eap.PEAP);
+ mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
+ assertEquals("PEAP_MSCHAPV2", mEnterpriseConfig.getKeyId(enterpriseConfig));
+ }
+
+ /** Verifies that passwords are not displayed in toString. */
+ @Test
+ public void passwordNotInToString() {
+ String password = "supersecret";
+ mEnterpriseConfig.setPassword(password);
+ assertFalse(mEnterpriseConfig.toString().contains(password));
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
new file mode 100644
index 0000000..a829eb9
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.WifiScanner.BssidInfo;
+import android.net.wifi.WifiScanner.BssidListener;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.util.test.BidirectionalAsyncChannelServer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiScanner}.
+ */
+@SmallTest
+public class WifiScannerTest {
+ @Mock
+ private Context mContext;
+ @Mock
+ private IWifiScanner mService;
+ @Mock
+ private BssidListener mBssidListener;
+
+ private WifiScanner mWifiScanner;
+ private TestLooper mLooper;
+ private Handler mHandler;
+
+ /**
+ * Setup before tests.
+ */
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mLooper = new TestLooper();
+ mHandler = mock(Handler.class);
+ BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
+ mContext, mLooper.getLooper(), mHandler);
+ when(mService.getMessenger()).thenReturn(server.getMessenger());
+ mWifiScanner = new WifiScanner(mContext, mService, mLooper.getLooper());
+ mLooper.dispatchAll();
+ }
+
+ /**
+ * Clean up after tests.
+ */
+ @After
+ public void cleanup() {
+ validateMockitoUsage();
+ }
+
+ private void verifySetHotlistMessage(Handler handler) {
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(handler, atLeastOnce()).handleMessage(messageCaptor.capture());
+ assertEquals("message.what is not CMD_SET_HOTLIST",
+ WifiScanner.CMD_SET_HOTLIST,
+ messageCaptor.getValue().what);
+ }
+
+ /**
+ * Test duplicate listeners for bssid tracking.
+ */
+ @Test
+ public void testStartTrackingBssidsDuplicateListeners() throws Exception {
+ BssidInfo[] bssids = new BssidInfo[] {
+ new BssidInfo()
+ };
+
+ // First start tracking succeeds.
+ mWifiScanner.startTrackingBssids(bssids, -100, mBssidListener);
+ mLooper.dispatchAll();
+ verifySetHotlistMessage(mHandler);
+
+ // Second start tracking should fail.
+ mWifiScanner.startTrackingBssids(bssids, -100, mBssidListener);
+ mLooper.dispatchAll();
+ verify(mBssidListener).onFailure(eq(WifiScanner.REASON_DUPLICATE_REQEUST), anyString());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
new file mode 100644
index 0000000..be11f0e
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.net.wifi.hotspot2;
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSP;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
+ */
+@SmallTest
+public class PasspointConfigurationTest {
+
+ private static HomeSP createHomeSp() {
+ HomeSP homeSp = new HomeSP();
+ homeSp.fqdn = "fqdn";
+ homeSp.friendlyName = "friendly name";
+ homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+ return homeSp;
+ }
+
+ private static Credential createCredential() {
+ Credential cred = new Credential();
+ cred.realm = "realm";
+ cred.userCredential = null;
+ cred.certCredential = null;
+ cred.simCredential = null;
+ cred.caCertificate = null;
+ cred.clientCertificateChain = null;
+ cred.clientPrivateKey = null;
+ return cred;
+ }
+
+ private static void verifyParcel(PasspointConfiguration writeConfig) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeConfig.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ PasspointConfiguration readConfig =
+ PasspointConfiguration.CREATOR.createFromParcel(parcel);
+ assertTrue(readConfig.equals(writeConfig));
+ }
+
+ @Test
+ public void verifyParcelWithDefault() throws Exception {
+ verifyParcel(new PasspointConfiguration());
+ }
+
+ @Test
+ public void verifyParcelWithHomeSPAndCredential() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = createHomeSp();
+ config.credential = createCredential();
+ verifyParcel(config);
+ }
+
+ @Test
+ public void verifyParcelWithHomeSPOnly() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = createHomeSp();
+ verifyParcel(config);
+ }
+
+ @Test
+ public void verifyParcelWithCredentialOnly() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.credential = createCredential();
+ verifyParcel(config);
+ }
+}
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
new file mode 100644
index 0000000..10b0267
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.net.wifi.hotspot2.omadm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.hotspot2.omadm.PPSMOParser;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSP;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}.
+ */
+@SmallTest
+public class PPSMOParserTest {
+ private static final String VALID_PPS_MO_XML_FILE = "assets/pps/PerProviderSubscription.xml";
+ private static final String PPS_MO_XML_FILE_DUPLICATE_HOMESP =
+ "assets/pps/PerProviderSubscription_DuplicateHomeSP.xml";
+ private static final String PPS_MO_XML_FILE_DUPLICATE_VALUE =
+ "assets/pps/PerProviderSubscription_DuplicateValue.xml";
+ private static final String PPS_MO_XML_FILE_MISSING_VALUE =
+ "assets/pps/PerProviderSubscription_MissingValue.xml";
+ private static final String PPS_MO_XML_FILE_MISSING_NAME =
+ "assets/pps/PerProviderSubscription_MissingName.xml";
+ private static final String PPS_MO_XML_FILE_INVALID_NODE =
+ "assets/pps/PerProviderSubscription_InvalidNode.xml";
+ private static final String PPS_MO_XML_FILE_INVALID_NAME =
+ "assets/pps/PerProviderSubscription_InvalidName.xml";
+
+ /**
+ * Read the content of the given resource file into a String.
+ *
+ * @param filename String name of the file
+ * @return String
+ * @throws IOException
+ */
+ private String loadResourceFile(String filename) throws IOException {
+ InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line).append("\n");
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Generate a {@link PasspointConfiguration} that matches the configuration specified in the
+ * XML file {@link #VALID_PPS_MO_XML_FILE}.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ private PasspointConfiguration generateConfigurationFromPPSMOTree() {
+ PasspointConfiguration config = new PasspointConfiguration();
+
+ // HomeSP configuration.
+ config.homeSp = new HomeSP();
+ config.homeSp.friendlyName = "Century House";
+ config.homeSp.fqdn = "mi6.co.uk";
+ config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
+
+ // Credential configuration.
+ config.credential = new Credential();
+ config.credential.realm = "shaken.stirred.com";
+ config.credential.userCredential = new Credential.UserCredential();
+ config.credential.userCredential.username = "james";
+ config.credential.userCredential.password = "Ym9uZDAwNw==";
+ config.credential.userCredential.eapType = 21;
+ config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
+ config.credential.certCredential = new Credential.CertificateCredential();
+ config.credential.certCredential.certType = "x509v3";
+ config.credential.certCredential.certSha256FingerPrint = new byte[32];
+ Arrays.fill(config.credential.certCredential.certSha256FingerPrint, (byte)0x1f);
+ config.credential.simCredential = new Credential.SimCredential();
+ config.credential.simCredential.imsi = "imsi";
+ config.credential.simCredential.eapType = 24;
+ return config;
+ }
+
+ /**
+ * Parse and verify all supported fields under PPS MO tree (currently only fields under
+ * HomeSP and Credential subtree).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void parseValidPPSMOTree() throws Exception {
+ String ppsMoTree = loadResourceFile(VALID_PPS_MO_XML_FILE);
+ PasspointConfiguration expectedConfig = generateConfigurationFromPPSMOTree();
+ PasspointConfiguration actualConfig = PPSMOParser.parseMOText(ppsMoTree);
+ assertTrue(actualConfig.equals(expectedConfig));
+ }
+
+ @Test
+ public void parseNullPPSMOTree() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(null));
+ }
+
+ @Test
+ public void parseEmptyPPSMOTree() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(new String()));
+ }
+
+ @Test
+ public void parsePPSMOTreeWithDuplicateHomeSP() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(
+ loadResourceFile(PPS_MO_XML_FILE_DUPLICATE_HOMESP)));
+ }
+
+ @Test
+ public void parsePPSMOTreeWithDuplicateValue() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(
+ loadResourceFile(PPS_MO_XML_FILE_DUPLICATE_VALUE)));
+ }
+
+ @Test
+ public void parsePPSMOTreeWithMissingValue() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(
+ loadResourceFile(PPS_MO_XML_FILE_MISSING_VALUE)));
+ }
+
+ @Test
+ public void parsePPSMOTreeWithMissingName() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(
+ loadResourceFile(PPS_MO_XML_FILE_MISSING_NAME)));
+ }
+
+ @Test
+ public void parsePPSMOTreeWithInvalidNode() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(
+ loadResourceFile(PPS_MO_XML_FILE_INVALID_NODE)));
+ }
+
+ @Test
+ public void parsePPSMOTreeWithInvalidName() throws Exception {
+ assertEquals(null, PPSMOParser.parseMOText(
+ loadResourceFile(PPS_MO_XML_FILE_INVALID_NAME)));
+ }
+}
+
+
+
+
+
+
+
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/XMLParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/XMLParserTest.java
new file mode 100644
index 0000000..c2dcec6
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/XMLParserTest.java
@@ -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.
+ */
+
+package android.net.wifi.hotspot2.omadm;
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.hotspot2.omadm.XMLNode;
+import android.net.wifi.hotspot2.omadm.XMLParser;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.omadm.XMLParser}.
+ */
+@SmallTest
+public class XMLParserTest {
+ XMLParser mParser;
+
+ private static XMLNode createNode(XMLNode parent, String tag, String text) {
+ XMLNode node = new XMLNode(parent, tag);
+ node.addText(text);
+ if (parent != null)
+ parent.addChild(node);
+ node.close();
+ return node;
+ }
+
+ /**
+ * Setup before tests.
+ */
+ @Before
+ public void setUp() throws Exception {
+ mParser = new XMLParser();
+ }
+
+ @Test(expected = IOException.class)
+ public void parseNullXML() throws Exception {
+ mParser.parse(null);
+ }
+
+ @Test(expected = IOException.class)
+ public void parseEmptyXML() throws Exception {
+ mParser.parse(new String());
+ }
+
+ @Test(expected = SAXException.class)
+ public void parseMalformedXML() throws Exception {
+ String malformedXmlTree = "<root><child1>test1</child2></root>";
+ mParser.parse(malformedXmlTree);
+ }
+
+ @Test
+ public void parseValidXMLTree() throws Exception {
+ String xmlTree = "<root><child1>test1</child1><child2>test2</child2></root>";
+
+ // Construct the expected XML tree.
+ XMLNode expectedRoot = createNode(null, "root", "");
+ createNode(expectedRoot, "child1", "test1");
+ createNode(expectedRoot, "child2", "test2");
+
+ XMLNode actualRoot = mParser.parse(xmlTree);
+ assertTrue(actualRoot.equals(expectedRoot));
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
new file mode 100644
index 0000000..68ac4ef
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.FakeKeys;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.CredentialTest}.
+ */
+@SmallTest
+public class CredentialTest {
+ private static Credential createCredential(Credential.UserCredential userCred,
+ Credential.CertificateCredential certCred,
+ Credential.SimCredential simCred,
+ X509Certificate caCert,
+ X509Certificate[] clientCertificateChain,
+ PrivateKey clientPrivateKey) {
+ Credential cred = new Credential();
+ cred.realm = "realm";
+ cred.userCredential = userCred;
+ cred.certCredential = certCred;
+ cred.simCredential = simCred;
+ cred.caCertificate = caCert;
+ cred.clientCertificateChain = clientCertificateChain;
+ cred.clientPrivateKey = clientPrivateKey;
+ return cred;
+ }
+
+ private static Credential createCredentialWithCertificateCredential() {
+ Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+ certCred.certType = "x509v3";
+ certCred.certSha256FingerPrint = new byte[256];
+ return createCredential(null, certCred, null, FakeKeys.CA_CERT0,
+ new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
+ }
+
+ private static Credential createCredentialWithSimCredential() {
+ Credential.SimCredential simCred = new Credential.SimCredential();
+ simCred.imsi = "imsi";
+ simCred.eapType = 1;
+ return createCredential(null, null, simCred, null, null, null);
+ }
+
+ private static Credential createCredentialWithUserCredential() {
+ Credential.UserCredential userCred = new Credential.UserCredential();
+ userCred.username = "username";
+ userCred.password = "password";
+ userCred.eapType = 1;
+ userCred.nonEapInnerMethod = "MS-CHAP";
+ return createCredential(userCred, null, null, FakeKeys.CA_CERT0,
+ new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
+ }
+
+ private static void verifyParcel(Credential writeCred) {
+ Parcel parcel = Parcel.obtain();
+ writeCred.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ Credential readCred = Credential.CREATOR.createFromParcel(parcel);
+ assertTrue(readCred.equals(writeCred));
+ }
+
+ @Test
+ public void verifyParcelWithDefault() throws Exception {
+ verifyParcel(new Credential());
+ }
+
+ @Test
+ public void verifyParcelWithCertificateCredential() throws Exception {
+ verifyParcel(createCredentialWithCertificateCredential());
+ }
+
+ @Test
+ public void verifyParcelWithSimCredential() throws Exception {
+ verifyParcel(createCredentialWithSimCredential());
+ }
+
+ @Test
+ public void verifyParcelWithUserCredential() throws Exception {
+ verifyParcel(createCredentialWithUserCredential());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
new file mode 100644
index 0000000..0d2da64
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.HashMap;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSP}.
+ */
+@SmallTest
+public class HomeSPTest {
+ private static HomeSP createHomeSp() {
+ HomeSP homeSp = new HomeSP();
+ homeSp.fqdn = "fqdn";
+ homeSp.friendlyName = "friendly name";
+ homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+ return homeSp;
+ }
+
+ private static void verifyParcel(HomeSP writeHomeSp) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeHomeSp.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ HomeSP readHomeSp = HomeSP.CREATOR.createFromParcel(parcel);
+ assertTrue(readHomeSp.equals(writeHomeSp));
+ }
+
+ @Test
+ public void verifyParcelWithEmptyHomeSP() throws Exception {
+ verifyParcel(new HomeSP());
+ }
+
+ @Test
+ public void verifyParcelWithValidHomeSP() throws Exception {
+ verifyParcel(createHomeSp());
+ }
+}